Spaces:
Running
Running
Show the character in the output panel while the clips generate
Browse files
app.py
CHANGED
|
@@ -44,8 +44,10 @@ EXAMPLES = [
|
|
| 44 |
|
| 45 |
PENDING = (
|
| 46 |
"<div style='display:flex;align-items:center;justify-content:center;height:560px;opacity:.55;"
|
| 47 |
-
"border:1px dashed var(--border-color-primary);border-radius:8px'>
|
| 48 |
)
|
|
|
|
|
|
|
| 49 |
IDLE_PANEL = (
|
| 50 |
"<div style='display:flex;align-items:center;justify-content:center;height:560px;opacity:.55;"
|
| 51 |
"border:1px dashed var(--border-color-primary);border-radius:8px'>"
|
|
@@ -53,6 +55,32 @@ IDLE_PANEL = (
|
|
| 53 |
)
|
| 54 |
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
def _out(name: str) -> str:
|
| 57 |
return os.path.join(OUTPUT_DIR, f"{int(time.time() * 1000)}-{name}")
|
| 58 |
|
|
@@ -111,7 +139,8 @@ def forge(
|
|
| 111 |
# ---- 1. the character design still ----
|
| 112 |
if uploaded:
|
| 113 |
character = uploaded
|
| 114 |
-
|
|
|
|
| 115 |
else:
|
| 116 |
yield PENDING, None, [], None, [], None, "Designing the character…"
|
| 117 |
backend = backends.BY_LABEL[backend_label]
|
|
@@ -126,7 +155,10 @@ def forge(
|
|
| 126 |
except Exception as error:
|
| 127 |
traceback.print_exc()
|
| 128 |
raise gr.Error(f"{backend.space} failed: {type(error).__name__}: {error}") from error
|
| 129 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
# ---- 2. one clip per move, concurrently ----
|
| 132 |
# These requests are fully independent, so they are fired together: the wall clock becomes the slowest single
|
|
@@ -213,7 +245,10 @@ def forge(
|
|
| 213 |
raise gr.Error("Every move failed. " + (failures[0] if failures else ""))
|
| 214 |
|
| 215 |
gallery = [(clip["video"], clip["label"]) for clip in clips]
|
| 216 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
# ---- 3. key, cut to extremes, pack ----
|
| 219 |
# One palette for the whole character, read before any keying. Shared across every move so the sheet cannot
|
|
|
|
| 44 |
|
| 45 |
PENDING = (
|
| 46 |
"<div style='display:flex;align-items:center;justify-content:center;height:560px;opacity:.55;"
|
| 47 |
+
"border:1px dashed var(--border-color-primary);border-radius:8px'>Designing your character…</div>"
|
| 48 |
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
IDLE_PANEL = (
|
| 52 |
"<div style='display:flex;align-items:center;justify-content:center;height:560px;opacity:.55;"
|
| 53 |
"border:1px dashed var(--border-color-primary);border-radius:8px'>"
|
|
|
|
| 55 |
)
|
| 56 |
|
| 57 |
|
| 58 |
+
def building_panel(character_path: str | None, message: str) -> str:
|
| 59 |
+
"""The output panel while the clips are still generating: the character, and what is happening to it.
|
| 60 |
+
|
| 61 |
+
The still is the first thing worth looking at and it exists a couple of minutes before the playable demo does,
|
| 62 |
+
so it goes straight into the main panel rather than staying folded away in the downloads accordion.
|
| 63 |
+
|
| 64 |
+
Embedded as a data URI, downscaled first. A file path here would have to go through `/gradio_api/file=`, which
|
| 65 |
+
is the route that already 403'd on this Space once, and the panel is re-yielded on every step — a 1024px PNG
|
| 66 |
+
would be a megabyte of base64 each time.
|
| 67 |
+
"""
|
| 68 |
+
if not character_path:
|
| 69 |
+
return PENDING
|
| 70 |
+
try:
|
| 71 |
+
thumbnail = Image.open(character_path).convert("RGBA")
|
| 72 |
+
thumbnail.thumbnail((460, 460))
|
| 73 |
+
source = sk.data_uri(thumbnail)
|
| 74 |
+
except Exception: # noqa: BLE001 — the panel is cosmetic; never fail a run over it
|
| 75 |
+
return PENDING
|
| 76 |
+
return (
|
| 77 |
+
"<div style='display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;"
|
| 78 |
+
"height:560px;border:1px dashed var(--border-color-primary);border-radius:8px'>"
|
| 79 |
+
"<style>@keyframes sf-pulse{0%,100%{opacity:.55}50%{opacity:1}}</style>"
|
| 80 |
+
f"<img src='{source}' alt='character' style='max-height:400px;max-width:90%;border-radius:6px'>"
|
| 81 |
+
f"<div style='animation:sf-pulse 1.6s ease-in-out infinite;font-size:.95em'>{message}</div>"
|
| 82 |
+
"</div>"
|
| 83 |
+
)
|
| 84 |
def _out(name: str) -> str:
|
| 85 |
return os.path.join(OUTPUT_DIR, f"{int(time.time() * 1000)}-{name}")
|
| 86 |
|
|
|
|
| 139 |
# ---- 1. the character design still ----
|
| 140 |
if uploaded:
|
| 141 |
character = uploaded
|
| 142 |
+
message = f"Animating {len(presets)} moves…"
|
| 143 |
+
yield building_panel(character, message), character, [], None, [], None, "Using your uploaded character."
|
| 144 |
else:
|
| 145 |
yield PENDING, None, [], None, [], None, "Designing the character…"
|
| 146 |
backend = backends.BY_LABEL[backend_label]
|
|
|
|
| 155 |
except Exception as error:
|
| 156 |
traceback.print_exc()
|
| 157 |
raise gr.Error(f"{backend.space} failed: {type(error).__name__}: {error}") from error
|
| 158 |
+
yield (
|
| 159 |
+
building_panel(character, f"Animating {len(presets)} moves…"),
|
| 160 |
+
character, [], None, [], None, f"Character designed. Animating {len(presets)} moves…",
|
| 161 |
+
)
|
| 162 |
|
| 163 |
# ---- 2. one clip per move, concurrently ----
|
| 164 |
# These requests are fully independent, so they are fired together: the wall clock becomes the slowest single
|
|
|
|
| 245 |
raise gr.Error("Every move failed. " + (failures[0] if failures else ""))
|
| 246 |
|
| 247 |
gallery = [(clip["video"], clip["label"]) for clip in clips]
|
| 248 |
+
yield (
|
| 249 |
+
building_panel(character, "Cutting to pose extremes…"),
|
| 250 |
+
character, gallery, None, [], None, f"{len(clips)} clips generated. Cutting to pose extremes…",
|
| 251 |
+
)
|
| 252 |
|
| 253 |
# ---- 3. key, cut to extremes, pack ----
|
| 254 |
# One palette for the whole character, read before any keying. Shared across every move so the sheet cannot
|