"""Viggle-Animate: replace the performers in a video with the person in a still. python inference/sample.py --cond driving.mp4 --ref character.png --out swapped.mp4 `--cond` supplies the motion, camera framing, background and lighting; `--ref` supplies who is in it. Everything about the render except the people is copied from `--cond`. The text encoder is never loaded. Conditioning comes from `assets/fixed_embed_fwd_anyframe.pt`, a frozen 362 x 5120 tensor computed once from the fixed prompt in `assets/fixed_prompt.txt`, so Qwen3-VL (63 GB of the base repo) stays on disk and the text block of the packed sequence is 362 rows instead of several thousand. There is no per-clip prompt and no caption: nothing in the output comes from text you write. Needs `--model-dir` pointing at a local copy of MiniMaxAI/MiniMax-H3 for the VAE, the audio VAE and the schedulers. This repository ships only the transformer and the LoRA. """ import argparse import os import time import torch from diffusers import MiniMaxH3Transformer3DModel, ModularPipeline from diffusers.modular_pipelines.minimax_h3 import MiniMaxH3ImageReference, MiniMaxH3VideoReference from diffusers.modular_pipelines.minimax_h3.encoders import MiniMaxH3Ref2VATextEncoderStep from diffusers.utils.export_utils import encode_video HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) parser = argparse.ArgumentParser() parser.add_argument("--cond", required=True, help="the video whose motion, framing and background are kept") parser.add_argument("--ref", required=True, help="a single still of the person to put in it") parser.add_argument("--out", required=True) parser.add_argument("--model-dir", required=True, help="a local copy of MiniMaxAI/MiniMax-H3, for the VAE / audio VAE / schedulers") parser.add_argument("--transformer", default=os.path.join(HERE, "transformer")) parser.add_argument("--lora", default=os.path.join(HERE, "lora")) parser.add_argument("--embed", default=os.path.join(HERE, "assets", "fixed_embed_fwd_anyframe.pt")) parser.add_argument("--num-frames", type=int, default=124, help="at 24 fps; 124 frames is ~5.2 s") parser.add_argument("--steps", type=int, default=4, help="the distilled student's operating point. More is not monotonically better: " "s4 is not a degraded s12") parser.add_argument("--flow-shift", type=float, default=3.0, help="the base model's released default is 12; the few-step student wants 3") parser.add_argument("--height", type=int, default=None, help="defaults to the conditioning clip's own height") parser.add_argument("--width", type=int, default=None, help="defaults to the conditioning clip's own width") parser.add_argument("--short-edge", type=int, default=None, help="the canvas both references are laid out on. Defaults to the conditioning clip's own " "short edge, which is what this model was evaluated at") parser.add_argument("--offload", action="store_true", help="stream the transformer from CPU in groups of 5 blocks: ~12 GB resident instead of 62") parser.add_argument("--seed", type=int, default=42) args = parser.parse_args() fixed = torch.load(args.embed, weights_only=False) def use_fixed_embeds(self, components, state): block_state = self.get_block_state(state) block_state.prompt_embeds = fixed["prompt_embeds"].to(components._execution_device, torch.bfloat16) block_state.text_token_tags = fixed["text_token_tags"] self.set_block_state(state, block_state) return components, state MiniMaxH3Ref2VATextEncoderStep.__call__ = use_fixed_embeds # The reference order is frozen: the presentation names `