"""Hugging Face Space for the 2D Motion Interface. Pick one of four real monocular videos; the ViTPose 2D keypoints shipped with the clip are fed through A_real -> 2D encoder -> MotionGPT and captioned live. The adapter-less baseline is captioned too, so the adapter's contribution is visible side by side. No pose estimation runs here: the keypoints are the ones released with the real-world dataset, so the Space is CPU-only and needs no ViTPose install. Env: BUNDLE_DIR local weight bundle (default: ./bundle, then ./space/bundle) BUNDLE_REPO HF model repo to pull the bundle from when BUNDLE_DIR is absent """ import html import os import gradio as gr from omegaconf import OmegaConf from os.path import join as pjoin from captioner import MotionCaptioner HERE = os.path.dirname(os.path.abspath(__file__)) ASSETS = pjoin(HERE, "assets") # A free account cannot host a Gradio Space on cpu-basic, so this Space runs on # ZeroGPU — which refuses to start unless at least one @spaces.GPU function exists. # Locally the `spaces` package is absent and everything falls back to CPU. try: import spaces GPU = spaces.GPU DEVICE = "cuda" except ImportError: # local / non-ZeroGPU def GPU(fn=None, **kwargs): return fn if fn is not None else (lambda f: f) DEVICE = "cpu" def _resolve_bundle(): for cand in (os.environ.get("BUNDLE_DIR"), pjoin(HERE, "bundle"), pjoin(HERE, "space", "bundle")): if cand and os.path.isdir(cand): return cand repo = os.environ.get("BUNDLE_REPO") if not repo: raise RuntimeError("No weight bundle found. Set BUNDLE_DIR or BUNDLE_REPO.") from huggingface_hub import snapshot_download return snapshot_download(repo_id=repo) CLIPS = {c.id: c for c in OmegaConf.load(pjoin(ASSETS, "clips.yaml")).clips} CHOICES = [(f"{c.label} ({c.frames_raw} frames)", cid) for cid, c in CLIPS.items()] CAPTIONER = MotionCaptioner(_resolve_bundle(), device=DEVICE) CSS = """ .result-card { border: 1px solid var(--border-color-primary); border-radius: var(--block-radius); background: var(--block-background-fill); padding: 24px 28px; min-height: 148px; display: flex; flex-direction: column; justify-content: center; } .result-label { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--body-text-color-subdued); margin-bottom: 12px; } .result-caption { font-size: clamp(20px, 2.4vw, 30px); line-height: 1.35; font-weight: 500; color: var(--body-text-color); margin: 0; text-wrap: balance; } .result-caption.placeholder { font-weight: 400; color: var(--body-text-color-subdued); } .result-meta { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border-color-primary); font-size: 12px; color: var(--body-text-color-subdued); font-variant-numeric: tabular-nums; } """ PLACEHOLDER = """
Select a clip and generate its caption.
{html.escape(out["caption"])}
\n' f' \n' f'