Spaces:
Running on Zero
Running on Zero
Download space/viewer.py from AlayaLab/FloodDiffusion2-Live: direct link, hf CLI and curl.
- Browser
- Download file 8.48 kB
-
https://huggingface.co/spaces/AlayaLab/FloodDiffusion2-Live/resolve/main/space/viewer.py
- Command line
-
hf download hf://spaces/AlayaLab/FloodDiffusion2-Live/space/viewer.py
-
curl -L -o viewer.py https://huggingface.co/spaces/AlayaLab/FloodDiffusion2-Live/resolve/main/space/viewer.py
8.48 kB
| """Persistent streaming mesh and skeleton viewer for Gradio 6.28.0.""" | |
| from pathlib import Path | |
| import gradio as gr | |
| HERE = Path(__file__).resolve().parent | |
| HTML = """ | |
| <div class="motion-viewer"> | |
| <div class="viewer-heading"> | |
| <span class="viewer-action">Motion</span> | |
| <span class="viewer-status" role="status">Ready</span> | |
| </div> | |
| <div class="viewer-stage"> | |
| <div class="viewer-focus-hint">Click the view to use WASD</div> | |
| <div class="viewer-body-notice"><span class="viewer-body-status" role="status"></span><button class="viewer-body-retry" type="button" hidden>Retry body loading</button></div> | |
| </div> | |
| <div class="viewer-controls"> | |
| <div class="viewer-key-legend"><span><kbd data-key="KeyW">W</kbd><kbd data-key="KeyS">S</kbd> Camera forward / back</span><span><kbd data-key="KeyA">A</kbd><kbd data-key="KeyD">D</kbd> Left / right</span><span><kbd data-key="Shift">Shift</kbd> Walk → run</span></div> | |
| <div class="viewer-slots" aria-label="Prompt selection"><span data-slot="0">1</span><span data-slot="1">2</span><span data-slot="2">3</span><span data-slot="3">4</span><span class="viewer-slot-label">Prompt 1</span></div> | |
| <span class="viewer-lead">Lead 0.00 s</span> | |
| </div> | |
| <div class="viewer-camera-hint">Drag to orbit · Scroll to zoom · Double-click to reset view</div> | |
| <div class="viewer-path-legend"><span class="path-reached">Reached</span><span class="path-ahead">Ahead</span><span class="path-target">◇ Target</span><span class="path-spring">○ Input</span></div> | |
| <div class="viewer-stats"> | |
| <div><span>Runtime</span><strong data-stat="runtime">0.0 s</strong></div> | |
| <div><span>Generation</span><strong data-stat="generation">— fps</strong></div> | |
| <div><span>Playback</span><strong data-stat="playback">0 fps</strong></div> | |
| <div><span>Buffer</span><strong data-stat="buffer">0.00 s</strong></div> | |
| </div> | |
| </div> | |
| """ | |
| CSS = """ | |
| .motion-viewer { overflow: hidden; border: 1px solid #dce3e8; border-radius: 16px; background: #f8fafb; color: #233340; font-family: inherit; } | |
| .viewer-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 20px 0; } | |
| .viewer-action { font-size: 16px; font-weight: 600; } | |
| .viewer-status { color: #667782; font-size: 12px; } | |
| .viewer-stage { position: relative; width: 100%; height: clamp(300px, 48vw, 490px); overflow: hidden; } | |
| .viewer-stage canvas { display: block; width: 100%; height: 100%; outline: none; cursor: grab; touch-action: none; } | |
| .viewer-stage canvas:active { cursor: grabbing; } | |
| .viewer-stage:focus-within { box-shadow: inset 0 0 0 2px #397ad5; } | |
| .viewer-focus-hint { position: absolute; bottom: 10px; left: 0; right: 0; z-index: 1; text-align: center; pointer-events: none; font-size: 12px; color: #647582; } | |
| .viewer-stage:focus-within .viewer-focus-hint { opacity: 0; } | |
| .viewer-body-notice { position: absolute; top: 12px; right: 18px; z-index: 1; color: #647582; font-size: 11px; display: flex; align-items: center; gap: 8px; } | |
| .viewer-body-retry { border: 1px solid #ced8df; border-radius: 5px; padding: 4px 7px; background: white; color: #445b6a; cursor: pointer; font: inherit; } | |
| .viewer-body-retry[hidden] { display: none; } | |
| .viewer-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 20px; padding: 12px 18px 5px; font-size: 12px; } | |
| .viewer-key-legend { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; } | |
| .viewer-key-legend > span { white-space: nowrap; } | |
| kbd { display: inline-block; min-width: 22px; margin-right: 3px; padding: 2px 5px; border: 1px solid #ced8df; border-radius: 4px; color: #445b6a; background: white; font: inherit; text-align: center; } | |
| kbd.is-down { color: white; background: #397ad5; border-color: #397ad5; } | |
| kbd[data-key="Shift"].is-down { background: #8b5cdd; border-color: #8b5cdd; } | |
| .viewer-slots { display: flex; align-items: center; gap: 5px; } | |
| .viewer-slots [data-slot] { width: 22px; height: 22px; line-height: 20px; text-align: center; border: 1px solid currentColor; border-radius: 50%; opacity: .6; } | |
| .viewer-slots [data-slot="0"] { color: #397ad5; } | |
| .viewer-slots [data-slot="1"] { color: #8b5cdd; } | |
| .viewer-slots [data-slot="2"] { color: #2d9e72; } | |
| .viewer-slots [data-slot="3"] { color: #d55a9a; } | |
| .viewer-slots [data-slot].is-selected { opacity: 1; border-width: 2px; font-weight: 700; line-height: 18px; } | |
| .viewer-slot-label { margin-left: 4px; } | |
| .viewer-lead { margin-left: auto; color: #667782; font-variant-numeric: tabular-nums; } | |
| .viewer-camera-hint { padding: 4px 18px 8px; color: #667782; font-size: 11px; } | |
| .viewer-path-legend { display: flex; gap: 16px; padding: 4px 18px 12px; color: #667782; font-size: 11px; } | |
| .viewer-path-legend span::before { content: ""; display: inline-block; width: 18px; margin-right: 5px; border-top: 2px solid #397ad5; vertical-align: middle; } | |
| .viewer-path-legend .path-reached::before { opacity: .55; } | |
| .viewer-path-legend .path-ahead::before { border-top-style: dashed; } | |
| .viewer-path-legend .path-target::before, .viewer-path-legend .path-spring::before { display: none; } | |
| .viewer-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid #e4eaee; background: #fff; } | |
| .viewer-stats > div { padding: 14px 16px; min-width: 0; } | |
| .viewer-stats span { display: block; font-size: 11px; color: #6c7b86; margin-bottom: 5px; } | |
| .viewer-stats strong { display: block; font-size: 17px; font-weight: 550; font-variant-numeric: tabular-nums; white-space: nowrap; } | |
| @media (max-width: 500px) { .viewer-heading { padding: 14px 14px 0; } .viewer-stats > div { padding: 12px 8px; } .viewer-stats strong { font-size: 14px; } .viewer-stats span { font-size: 10px; } } | |
| """ | |
| def create_viewer(*, require_body_ready: bool = False) -> gr.HTML: | |
| """Return one HTML output; yield native dictionaries to update its value. | |
| Each packet contains session_id, sequence, start_frame, frames [T,22,3], | |
| fps, generated_frames, elapsed_seconds, inference_fps, action and status. | |
| Frames use world XYZ, with Y up. A new session_id resets playback. | |
| mesh_pose contains rotations (base64 float32 LE [T,22,3,3], row-major local | |
| matrices with the world root first), origins (world pelvis [T,3]), and | |
| frame_count=T. The fixed neutral body is loaded once and deformed on each | |
| displayed frame using all skinning weights and the full body pose corrective. | |
| With require_body_ready=True, bind viewer.body_ready to enable Start only | |
| after the browser has loaded the body; loading assets consumes no GPU time. | |
| Optional mesh_topology contains faces (base64 uint16 LE), vertex_count=6890 | |
| and face_count=13776, sent once per session. Optional mesh contains vertices | |
| (base64 float16 LE, C-order [T,6890,3], relative to each supplied root), | |
| origins [T,3] in world XYZ, frame_count=T and vertex_count=6890. Mesh, joints | |
| and frame_meta share start_frame and one playback queue. Canvas fallback | |
| and packets without mesh continue to display the skeleton. | |
| Optional control_points and frame_meta retain backend coordinates unchanged. | |
| With control_points_delta=True, points are merged by frame (up to 2000); | |
| otherwise control_points is a complete replacement. New sessions reset it. | |
| Bind ``viewer.control(fn, inputs=[], outputs=[], queue=False, | |
| trigger_mode="multiple")``; an annotated gr.EventData receives the control | |
| dictionary through ``evt._data``. This event never writes ``value``. | |
| """ | |
| vendor = (HERE / "three.min.js").read_text(encoding="utf-8") | |
| body_script = (HERE / "body.js").read_text(encoding="utf-8") | |
| script = (HERE / "viewer.js").read_text(encoding="utf-8") | |
| body_manifest = HERE / "assets" / "body.json" | |
| body_data = HERE / "assets" / "body.bin" | |
| gr.set_static_paths(paths=[body_manifest, body_data]) | |
| # The local UMD build is loaded once; no CDN or file-serving setup is needed. | |
| js = "if (!globalThis.THREE) {\n" + vendor + "\n}\n" + body_script + "\n" + script | |
| return gr.HTML( | |
| value={"session_id": "", "sequence": -1, "frames": [], "status": "idle"}, | |
| html_template=HTML, | |
| css_template=CSS, | |
| js_on_load=js, | |
| apply_default_css=False, | |
| container=False, | |
| padding=False, | |
| elem_id="live-motion-viewer", | |
| body_manifest_url="/gradio_api/file=" + body_manifest.resolve().as_posix(), | |
| body_data_url="/gradio_api/file=" + body_data.resolve().as_posix(), | |
| require_body_ready=require_body_ready, | |
| ) | |