142 lines
4.6 KiB
Python
Executable File
142 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python
|
||
"""
|
||
orbit_poc.py — 2.5D actor orbit preview proof-of-concept.
|
||
|
||
Usage:
|
||
python orbit_poc.py --input img1.png img2.png ... --output ./output
|
||
python orbit_poc.py --input ./filmstrip_images/ --output ./output --mode swing --frames 36
|
||
|
||
Output:
|
||
./output/orbit_frames/frame_NNN.png
|
||
./output/orbit_preview.mp4
|
||
./output/debug/selected_frame.png
|
||
./output/debug/actor_rgba.png
|
||
./output/debug/mask.png
|
||
./output/debug/depth.png
|
||
./output/debug/depth_colorized.png
|
||
"""
|
||
|
||
import argparse
|
||
import glob
|
||
import os
|
||
import sys
|
||
import time
|
||
|
||
# Ensure tour-comfy is on the path when running from project root
|
||
_here = os.path.dirname(os.path.abspath(__file__))
|
||
if _here not in sys.path:
|
||
sys.path.insert(0, _here)
|
||
|
||
from orbit_module import run_orbit_pipeline
|
||
|
||
|
||
_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff", ".tif"}
|
||
|
||
|
||
def _collect_inputs(raw_inputs: list) -> list:
|
||
"""Expand dirs and glob patterns; return sorted list of image paths."""
|
||
paths = []
|
||
for item in raw_inputs:
|
||
if os.path.isdir(item):
|
||
for fname in sorted(os.listdir(item)):
|
||
if os.path.splitext(fname)[1].lower() in _IMAGE_EXTS:
|
||
paths.append(os.path.join(item, fname))
|
||
elif "*" in item or "?" in item:
|
||
paths.extend(sorted(glob.glob(item)))
|
||
elif os.path.isfile(item):
|
||
paths.append(item)
|
||
else:
|
||
print(f"[warn] not found: {item}", file=sys.stderr)
|
||
return paths
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="Generate a 2.5D orbit preview (depth-card parallax) from actor images."
|
||
)
|
||
parser.add_argument(
|
||
"--input", "-i", nargs="+", required=True,
|
||
metavar="PATH",
|
||
help="Input image paths, glob patterns, or directories",
|
||
)
|
||
parser.add_argument(
|
||
"--output", "-o", default="./output",
|
||
metavar="DIR",
|
||
help="Output directory (default: ./output)",
|
||
)
|
||
parser.add_argument(
|
||
"--frames", "-f", type=int, default=36,
|
||
help="Number of animation frames (default: 36)",
|
||
)
|
||
parser.add_argument(
|
||
"--fps", type=int, default=24,
|
||
help="Output video framerate (default: 24)",
|
||
)
|
||
parser.add_argument(
|
||
"--parallax", "-p", type=float, default=0.08,
|
||
help="Parallax strength 0–1, fraction of image width (default: 0.08)",
|
||
)
|
||
parser.add_argument(
|
||
"--angle", "-a", type=float, default=35.0,
|
||
help="Maximum orbit angle in degrees for swing mode (default: 35)",
|
||
)
|
||
parser.add_argument(
|
||
"--mode", "-m", choices=["swing", "orbit"], default="swing",
|
||
help="'swing' = left↔right loop (default), 'orbit' = full 360°",
|
||
)
|
||
parser.add_argument(
|
||
"--no-debug", action="store_true",
|
||
help="Skip writing debug output files",
|
||
)
|
||
|
||
args = parser.parse_args()
|
||
|
||
image_paths = _collect_inputs(args.input)
|
||
if not image_paths:
|
||
print("[error] No input images found.", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
print(f"[orbit] {len(image_paths)} input image(s)")
|
||
for p in image_paths[:5]:
|
||
print(f" {p}")
|
||
if len(image_paths) > 5:
|
||
print(f" ... ({len(image_paths) - 5} more)")
|
||
|
||
print(f"[orbit] output dir : {os.path.abspath(args.output)}")
|
||
print(f"[orbit] frames={args.frames} fps={args.fps} mode={args.mode} "
|
||
f"parallax={args.parallax} angle±{args.angle}°")
|
||
|
||
# Use the first image as the primary input (CLI can pass multiple; first = best choice)
|
||
primary = image_paths[0]
|
||
if len(image_paths) > 1:
|
||
print(f"[orbit] using first image as primary: {primary}")
|
||
print(f" (pass a single image or the specific frame you want)")
|
||
|
||
t0 = time.perf_counter()
|
||
result = run_orbit_pipeline(
|
||
image_path=primary,
|
||
output_dir=args.output,
|
||
n_frames=args.frames,
|
||
parallax_strength=args.parallax,
|
||
mode=args.mode,
|
||
fps=args.fps,
|
||
max_angle_deg=args.angle,
|
||
debug=not args.no_debug,
|
||
)
|
||
elapsed = time.perf_counter() - t0
|
||
|
||
print(f"\n[orbit] done in {elapsed:.1f}s")
|
||
print(f" actor : {result['actor_path']}")
|
||
print(f" has alpha : {result['has_alpha']}")
|
||
print(f" has bg plate : {result['has_bg']}")
|
||
print(f" frames dir : {result['frames_dir']} ({result['n_frames']} PNGs)")
|
||
print(f" video : {result['video_path']}")
|
||
if not args.no_debug:
|
||
print(f" debug dir : {result['debug_dir']}")
|
||
if not result['has_alpha']:
|
||
print(f"\n TIP: Use 'No BG' on this image first for a much better orbit effect.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|