72 lines
3.0 KiB
Python
72 lines
3.0 KiB
Python
#!/usr/bin/env python
|
||
"""
|
||
orbit_qwen_poc.py — near-real turntable test via Qwen-Image-Edit.
|
||
|
||
python orbit_qwen_poc.py --input front.png --output ./out \
|
||
--views 12 --mode turntable --interp 4 --seed 42
|
||
|
||
Generates one re-rendered view per yaw angle (anchored to the input, fixed seed),
|
||
flow-interpolates for smoothness, and stitches a looping MP4.
|
||
"""
|
||
|
||
import argparse
|
||
import os
|
||
import sys
|
||
import time
|
||
|
||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||
if _HERE not in sys.path:
|
||
sys.path.insert(0, _HERE)
|
||
|
||
from orbit_qwen import run_qwen_orbit
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser(description="Qwen turntable (near-real subject turning).")
|
||
ap.add_argument("--input", "-i", required=True, help="Front-facing source image")
|
||
ap.add_argument("--output", "-o", default="./out_turntable", help="Output directory")
|
||
ap.add_argument("--views", "-v", type=int, default=24, help="Qwen keyframes (yaw steps)")
|
||
ap.add_argument("--mode", "-m", choices=["turntable", "swing"], default="turntable",
|
||
help="turntable=full 360 loop, swing=front-facing arc only")
|
||
ap.add_argument("--sweep", type=float, default=180.0, help="swing arc width in degrees")
|
||
ap.add_argument("--anchor", choices=["original", "chain"], default="original",
|
||
help="original=each view from source (stable), chain=from previous (smoother)")
|
||
ap.add_argument("--interp", type=int, default=1,
|
||
help="interpolated frames per keyframe gap (1=none; >1 ghosts, not advised)")
|
||
ap.add_argument("--no-smooth", action="store_true", help="crossfade instead of optical-flow morph")
|
||
ap.add_argument("--fps", type=int, default=12)
|
||
ap.add_argument("--seed", type=int, default=42)
|
||
ap.add_argument("--steps", type=int, default=8, help="Qwen sampler steps (4 fast, 8 nicer)")
|
||
ap.add_argument("--max-area", type=int, default=0, help="output pixel budget (0=API default)")
|
||
args = ap.parse_args()
|
||
|
||
if not os.path.exists(args.input):
|
||
print(f"[error] input not found: {args.input}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
def prog(i, n, deg):
|
||
print(f" [{i+1}/{n}] rendering {int(deg):3d}°…", flush=True)
|
||
|
||
print(f"[turntable] input : {args.input}")
|
||
print(f"[turntable] mode={args.mode} views={args.views} anchor={args.anchor} "
|
||
f"interp×{args.interp} seed={args.seed} steps={args.steps}")
|
||
|
||
t0 = time.perf_counter()
|
||
res = run_qwen_orbit(
|
||
image_path=args.input, output_dir=args.output,
|
||
n_views=args.views, seed=args.seed, mode=args.mode, sweep_deg=args.sweep,
|
||
anchor=args.anchor, interp_factor=args.interp, smooth=not args.no_smooth,
|
||
fps=args.fps, max_area=args.max_area, steps=args.steps, on_progress=prog,
|
||
)
|
||
dt = time.perf_counter() - t0
|
||
|
||
print(f"\n[turntable] done in {dt:.1f}s "
|
||
f"({dt/max(1,res['n_views']):.1f}s/view)")
|
||
print(f" keyframes : {res['n_views']} → {res['views_dir']}")
|
||
print(f" frames : {res['n_frames']} (after interpolation)")
|
||
print(f" video : {res['video_path']}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|