AlterProgramming's picture
add txt2img tab + infer_txt2img endpoint (SD 1.5)
7faaef2 verified
Raw
History Blame Contribute Delete
8.09 kB
"""studio CLI — verbs mirror cursor ops.
Subcommands:
open <image> validate; print cursor state
compile <kf0> <kf1> -o <out.npz> two-keyframe timing-layer compile
export <frames.npz> -o <out.gif|.mp4|.webm> render FrameStack to disk
"""
from __future__ import annotations
import argparse
import sys
import json
from pixel_cursor import open_cursor, ease_between, load_image
from . import Studio
from .io import save_framestack_npz, load_framestack_npz, export_video
def cmd_open(args: argparse.Namespace) -> int:
img = load_image(args.image)
cur = open_cursor(img)
print(f"opened: {img}")
for k, v in cur.tell().items():
print(f" {k}: {v}")
return 0
def cmd_compile(args: argparse.Namespace) -> int:
kf0 = load_image(args.kf0)
kf1 = load_image(args.kf1)
cur_a = open_cursor(kf0).at_frame(0)
cur_b = open_cursor(kf1).at_frame(args.frames - 1)
seg = ease_between(0, args.frames, curve=args.curve, stride=args.stride)
stack = Studio().compile_24fps_from_keyframes([cur_a, cur_b], [seg])
save_framestack_npz(stack, args.out)
print(f"compiled {stack.num_frames} frames @ {stack.fps}fps -> {args.out}")
print(f" curve={args.curve} stride={args.stride} (on-{['ones','twos','threes'][min(args.stride-1, 2)]})")
return 0
def cmd_export(args: argparse.Namespace) -> int:
stack = load_framestack_npz(args.frames)
out = export_video(stack, args.out)
print(f"exported {stack.num_frames} frames @ {stack.fps}fps -> {out}")
return 0
def cmd_sheet(args: argparse.Namespace) -> int:
from .spritesheet import write_sprite_sheet, sheet_metadata
stack = load_framestack_npz(args.frames)
out = write_sprite_sheet(stack, args.out, cols=args.cols)
meta = sheet_metadata(stack, cols=args.cols)
print(f"sheet -> {out}")
print(json.dumps(meta, indent=2))
if args.manifest:
manifest_path = args.manifest
with open(manifest_path, "w") as f:
json.dump(meta, f, indent=2)
print(f"manifest -> {manifest_path}")
return 0
def cmd_motion(args: argparse.Namespace) -> int:
from .backends.animatediff import AnimateDiffAdapter, MOTION_LORA_MAP
from .backends.hf_space import HFSpaceAdapter
if args.list_presets:
print("MotionLoRA presets (shared by `animatediff` and `hf_space` backends):")
for name, lora_id in MOTION_LORA_MAP.items():
print(f" {name:24s} -> {lora_id}")
return 0
if args.preset is None or args.image is None:
print(
"error: --preset and --image are required (or use --list-presets)",
file=sys.stderr,
)
return 2
if args.backend == "hf_space":
if not args.space:
print("error: --space <owner>/<name> is required for --backend hf_space", file=sys.stderr)
return 2
adapter = HFSpaceAdapter(space_id=args.space, hf_token=args.hf_token)
else:
adapter = AnimateDiffAdapter()
adapter.register()
img = load_image(args.image)
cur = open_cursor(img).bind_motion_exemplar([args.preset], backend=args.backend)
motion_spec = {
"num_frames": args.num_frames,
"num_inference_steps": args.steps,
"guidance_scale": args.guidance,
"prompt": args.prompt,
"negative_prompt": args.negative_prompt,
"seed": args.seed,
}
if args.dry_run:
diag = adapter.dry_run(cur, motion_spec)
print(json.dumps(diag, indent=2, default=str))
return 0
if args.out is None:
print("error: -o/--out is required unless --dry-run is set", file=sys.stderr)
return 2
stack = cur.write_motion(motion_spec, backend=args.backend)
save_framestack_npz(stack, args.out)
print(f"generated {stack.num_frames} frames @ {stack.fps}fps -> {args.out}")
print(f" backend={args.backend} preset={args.preset}")
return 0
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="studio",
description="Pixel-cursor animation studio (v0: model-free timing compile)",
)
sub = p.add_subparsers(dest="cmd", required=True)
p_open = sub.add_parser("open", help="Load an image; print cursor state")
p_open.add_argument("image")
p_open.set_defaults(func=cmd_open)
p_compile = sub.add_parser(
"compile",
help="Compile a two-keyframe segment into a FrameStack via the cursor timing layer",
)
p_compile.add_argument("kf0", help="first keyframe image")
p_compile.add_argument("kf1", help="second keyframe image")
p_compile.add_argument("-o", "--out", required=True, help="output .npz path")
p_compile.add_argument("--frames", type=int, default=24, help="total frames (default 24)")
p_compile.add_argument(
"--curve",
default="ease",
choices=("linear", "ease", "ease-in", "ease-out", "spring"),
)
p_compile.add_argument(
"--stride",
type=int,
default=2,
help="on-twos=2 (default), on-ones=1, on-threes=3",
)
p_compile.add_argument("--fps", type=int, default=24, help="target fps (default 24)")
p_compile.set_defaults(func=cmd_compile)
p_export = sub.add_parser(
"export", help="Render a FrameStack .npz to a video file"
)
p_export.add_argument("frames", help="input .npz from `studio compile`")
p_export.add_argument(
"-o", "--out", required=True, help="output .gif | .mp4 | .webm | .mkv | .avi"
)
p_export.set_defaults(func=cmd_export)
p_sheet = sub.add_parser(
"sheet",
help="Pack a FrameStack .npz into a sprite-sheet PNG (Babylon SpriteManager friendly)",
)
p_sheet.add_argument("frames", help="input .npz from `studio compile` or `studio motion`")
p_sheet.add_argument("-o", "--out", required=True, help="output .png path")
p_sheet.add_argument("--cols", type=int, default=4, help="frames per row in the grid (default 4)")
p_sheet.add_argument("--manifest", help="optional .json manifest path (cell size, fps, loop ms)")
p_sheet.set_defaults(func=cmd_sheet)
p_motion = sub.add_parser(
"motion",
help="Generate motion on a single image via AnimateDiff MotionLoRA preset",
)
p_motion.add_argument(
"--list-presets", action="store_true", help="List all 8 verified MotionLoRA presets and exit"
)
p_motion.add_argument("--preset", help="preset name (see --list-presets)")
p_motion.add_argument("--image", help="source image path")
p_motion.add_argument("-o", "--out", help="output .npz path (required unless --dry-run)")
p_motion.add_argument(
"--dry-run",
action="store_true",
help="Print what would happen + resource estimate; no model load, no output written",
)
p_motion.add_argument("--num-frames", type=int, default=16)
p_motion.add_argument("--steps", type=int, default=25, help="num_inference_steps")
p_motion.add_argument("--guidance", type=float, default=7.5, help="guidance_scale")
p_motion.add_argument("--prompt", default="high quality, detailed")
p_motion.add_argument("--negative-prompt", default="bad quality, blurry")
p_motion.add_argument("--seed", type=int, default=42)
p_motion.add_argument(
"--backend",
default="animatediff",
choices=("animatediff", "hf_space"),
help="local diffusers run (animatediff) or remote ZeroGPU Space (hf_space)",
)
p_motion.add_argument(
"--space", default=None,
help="HF Space id (e.g. owner/venture-studio); required when --backend hf_space",
)
p_motion.add_argument(
"--hf-token", default=None,
help="HF token (overrides HF_TOKEN env var and ~/.cache/huggingface/token)",
)
p_motion.set_defaults(func=cmd_motion)
return p
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
sys.exit(main())