Deploy deathmatch from 46b2b8c
Browse files- app.py +13 -6
- model_runtime.py +8 -1
app.py
CHANGED
|
@@ -52,10 +52,11 @@ body, .gradio-container {
|
|
| 52 |
.dm-hero h1 {
|
| 53 |
margin: 0; font-family: 'Anton', sans-serif; font-size: clamp(46px, 8vw, 92px);
|
| 54 |
line-height: 0.9; letter-spacing: 0.01em; text-transform: uppercase;
|
| 55 |
-
|
| 56 |
-
-
|
|
|
|
| 57 |
}
|
| 58 |
-
.dm-hero p { margin: 12px 0 0; color:
|
| 59 |
.dm-status {
|
| 60 |
margin: 14px 0 4px; padding: 11px 14px; border-radius: 14px; font-size: 12px;
|
| 61 |
font-family: 'JetBrains Mono', monospace; border: 1px solid rgba(255,255,255,0.1);
|
|
@@ -160,11 +161,12 @@ def _winner_html(card: dict) -> str:
|
|
| 160 |
f"<div class='r'>{card.get('winner_reason','')}</div></div>")
|
| 161 |
|
| 162 |
|
| 163 |
-
def run_fight(image_a, image_b, storyline, arena, style):
|
| 164 |
if image_a is None or image_b is None:
|
| 165 |
raise gr.Error("Upload a photo for BOTH fighters first.")
|
| 166 |
try:
|
| 167 |
-
card = generate_fightcard(image_a, image_b, storyline, arena, style
|
|
|
|
| 168 |
frames = generate_keyframes(card, style)
|
| 169 |
except (RuntimeError, BackendError) as e:
|
| 170 |
raise gr.Error(f"Fight booking failed: {e}")
|
|
@@ -207,6 +209,11 @@ with gr.Blocks(title="Celebrity Deathmatch") as demo:
|
|
| 207 |
with gr.Row():
|
| 208 |
img_a = gr.Image(label="Fighter A", type="pil")
|
| 209 |
img_b = gr.Image(label="Fighter B", type="pil")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
with gr.Row():
|
| 211 |
storyline = gr.Textbox(label="Backstory / beef (optional)", max_lines=2,
|
| 212 |
placeholder="e.g. They feuded over a sampled hook")
|
|
@@ -232,7 +239,7 @@ with gr.Blocks(title="Celebrity Deathmatch") as demo:
|
|
| 232 |
export_file = gr.File(label="Fight card")
|
| 233 |
|
| 234 |
fight_btn.click(
|
| 235 |
-
run_fight, [img_a, img_b, storyline, arena, style],
|
| 236 |
[card_state, frames_state, tale, reel, commentary, animate_btn, tabs],
|
| 237 |
)
|
| 238 |
animate_btn.click(
|
|
|
|
| 52 |
.dm-hero h1 {
|
| 53 |
margin: 0; font-family: 'Anton', sans-serif; font-size: clamp(46px, 8vw, 92px);
|
| 54 |
line-height: 0.9; letter-spacing: 0.01em; text-transform: uppercase;
|
| 55 |
+
color: #ffc233;
|
| 56 |
+
text-shadow: 0 0 22px rgba(255, 150, 30, 0.6), 0 0 6px rgba(255, 200, 60, 0.5),
|
| 57 |
+
0 2px 4px rgba(0, 0, 0, 0.55);
|
| 58 |
}
|
| 59 |
+
.dm-hero p { margin: 12px 0 0; color: #ffd98a; font-size: 16px; max-width: 640px; }
|
| 60 |
.dm-status {
|
| 61 |
margin: 14px 0 4px; padding: 11px 14px; border-radius: 14px; font-size: 12px;
|
| 62 |
font-family: 'JetBrains Mono', monospace; border: 1px solid rgba(255,255,255,0.1);
|
|
|
|
| 161 |
f"<div class='r'>{card.get('winner_reason','')}</div></div>")
|
| 162 |
|
| 163 |
|
| 164 |
+
def run_fight(image_a, image_b, storyline, arena, style, name_a, name_b):
|
| 165 |
if image_a is None or image_b is None:
|
| 166 |
raise gr.Error("Upload a photo for BOTH fighters first.")
|
| 167 |
try:
|
| 168 |
+
card = generate_fightcard(image_a, image_b, storyline, arena, style,
|
| 169 |
+
name_a, name_b)
|
| 170 |
frames = generate_keyframes(card, style)
|
| 171 |
except (RuntimeError, BackendError) as e:
|
| 172 |
raise gr.Error(f"Fight booking failed: {e}")
|
|
|
|
| 209 |
with gr.Row():
|
| 210 |
img_a = gr.Image(label="Fighter A", type="pil")
|
| 211 |
img_b = gr.Image(label="Fighter B", type="pil")
|
| 212 |
+
with gr.Row():
|
| 213 |
+
name_a = gr.Textbox(label="Fighter A name (optional)",
|
| 214 |
+
placeholder="auto-detected if left blank")
|
| 215 |
+
name_b = gr.Textbox(label="Fighter B name (optional)",
|
| 216 |
+
placeholder="auto-detected if left blank")
|
| 217 |
with gr.Row():
|
| 218 |
storyline = gr.Textbox(label="Backstory / beef (optional)", max_lines=2,
|
| 219 |
placeholder="e.g. They feuded over a sampled hook")
|
|
|
|
| 239 |
export_file = gr.File(label="Fight card")
|
| 240 |
|
| 241 |
fight_btn.click(
|
| 242 |
+
run_fight, [img_a, img_b, storyline, arena, style, name_a, name_b],
|
| 243 |
[card_state, frames_state, tale, reel, commentary, animate_btn, tabs],
|
| 244 |
)
|
| 245 |
animate_btn.click(
|
model_runtime.py
CHANGED
|
@@ -71,13 +71,18 @@ def health() -> dict:
|
|
| 71 |
return {"status": "unreachable", "error": str(e), "url": API_URL}
|
| 72 |
|
| 73 |
|
| 74 |
-
def generate_fightcard(image_a, image_b, storyline: str, arena: str, style: str
|
|
|
|
| 75 |
"""Stage 1 — two photos -> validated fight card dict."""
|
| 76 |
if MOCK:
|
| 77 |
import copy
|
| 78 |
from mock import MOCK_FIGHTCARD
|
| 79 |
card = copy.deepcopy(MOCK_FIGHTCARD)
|
| 80 |
card["arena"] = arena or card["arena"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
return card
|
| 82 |
data = _post("/fightcard", {
|
| 83 |
"image_a_b64": _pil_to_b64(image_a),
|
|
@@ -85,6 +90,8 @@ def generate_fightcard(image_a, image_b, storyline: str, arena: str, style: str)
|
|
| 85 |
"storyline": (storyline or "")[:500],
|
| 86 |
"arena": arena,
|
| 87 |
"style": style,
|
|
|
|
|
|
|
| 88 |
})
|
| 89 |
return data["fightcard"]
|
| 90 |
|
|
|
|
| 71 |
return {"status": "unreachable", "error": str(e), "url": API_URL}
|
| 72 |
|
| 73 |
|
| 74 |
+
def generate_fightcard(image_a, image_b, storyline: str, arena: str, style: str,
|
| 75 |
+
name_a: str = "", name_b: str = "") -> dict:
|
| 76 |
"""Stage 1 — two photos -> validated fight card dict."""
|
| 77 |
if MOCK:
|
| 78 |
import copy
|
| 79 |
from mock import MOCK_FIGHTCARD
|
| 80 |
card = copy.deepcopy(MOCK_FIGHTCARD)
|
| 81 |
card["arena"] = arena or card["arena"]
|
| 82 |
+
if name_a and name_a.strip():
|
| 83 |
+
card["fighter_a"]["name"] = name_a.strip()
|
| 84 |
+
if name_b and name_b.strip():
|
| 85 |
+
card["fighter_b"]["name"] = name_b.strip()
|
| 86 |
return card
|
| 87 |
data = _post("/fightcard", {
|
| 88 |
"image_a_b64": _pil_to_b64(image_a),
|
|
|
|
| 90 |
"storyline": (storyline or "")[:500],
|
| 91 |
"arena": arena,
|
| 92 |
"style": style,
|
| 93 |
+
"name_a": (name_a or "").strip()[:60],
|
| 94 |
+
"name_b": (name_b or "").strip()[:60],
|
| 95 |
})
|
| 96 |
return data["fightcard"]
|
| 97 |
|