File size: 7,854 Bytes
b96d4b9 9a53105 b96d4b9 9a53105 b96d4b9 9a53105 b96d4b9 9a53105 b96d4b9 9a53105 b96d4b9 9a53105 b96d4b9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | """Celebrity Deathmatch β mock data + CPU placeholder renderers (DEATHMATCH_MOCK=1).
Lets the whole UX run on CPU in seconds with no backend: a canned fight card,
labelled placeholder keyframes, and a mock "fight video" (animated GIF cycling
the keyframes with commentary captions burned in β proving the captions decision).
Real models swap in behind the same model_runtime signatures.
"""
from __future__ import annotations
import os
import tempfile
import textwrap
from PIL import Image, ImageDraw, ImageFont
# Canned Stage-1 output β the marquee matchup. Same shape as the backend FightCard.
MOCK_FIGHTCARD = {
"fighter_a": {
"name": "Britney Spears",
"appearance": "blonde clay figure with a high ponytail, sparkly red bodysuit, "
"headset microphone, confident pop-diva pose",
"persona": "Unbothered pop royalty who turns every hit into choreography",
"signature_move": "The Toxic Spin Kick",
"stats": {"power": 6, "speed": 9, "showmanship": 10},
},
"fighter_b": {
"name": "Eminem",
"appearance": "lanky clay figure with bleached buzzcut, white tank top, baggy jeans, "
"permanent scowl, fists raised",
"persona": "Rapid-fire trash-talker who fights to a 140 BPM flow",
"signature_move": "The Rap God Combo",
"stats": {"power": 8, "speed": 8, "showmanship": 7},
},
"arena": "Classic Deathmatch ring",
"beats": [
{"id": 1, "title": "The stare-down / entrances",
"action": "Britney moonwalks into the ring under a glitter cannon while Eminem "
"stomps in from the opposite corner, cracking his clay knuckles",
"commentary": [
{"speaker": "JOHNNY", "line": "Pop royalty versus the Rap God β "
"and the glitter cannon is FIRING!"},
{"speaker": "NICK", "line": "There's confetti in Slim's eyes already, Johnny."}],
"video_prompt": "two clay fighters enter a wrestling ring, glitter falling, slow zoom"},
{"id": 2, "title": "First blood",
"action": "Eminem fires off a flurry of word-bullet punches; Britney back-flips "
"away and flicks her ponytail like a whip",
"commentary": [
{"speaker": "JOHNNY", "line": "Rapid-fire jabs from Eminem!"},
{"speaker": "NICK", "line": "And the ponytail snaps back like a bullwhip. Cute."}],
"video_prompt": "fast punches, clay fighter dodges with a backflip, motion blur"},
{"id": 3, "title": "Signature move",
"action": "Britney launches the Toxic Spin Kick, a neon-green tornado of legs "
"that sends Eminem bouncing off the ropes",
"commentary": [
{"speaker": "JOHNNY", "line": "THE TOXIC SPIN KICK! It's a green tornado of doom!"},
{"speaker": "NICK", "line": "Slim Shady is ping-ponging off the ropes, folks."}],
"video_prompt": "spinning kick, green energy tornado, opponent flung into ropes"},
{"id": 4, "title": "Comeback / turning point",
"action": "Eminem shakes it off and unleashes the Rap God Combo β fists moving "
"so fast they blur into a clay smear",
"commentary": [
{"speaker": "JOHNNY", "line": "Here comes the Rap God Combo β his hands are a BLUR!"},
{"speaker": "NICK", "line": "Her tiara just got rearranged."}],
"video_prompt": "blurred rapid punch combo, clay smear trails, dramatic angle"},
{"id": 5, "title": "Finishing move + KO",
"action": "Britney counters mid-combo, drops the beat AND Eminem with one final "
"spin, then strikes a victory pose on his flattened clay body",
"commentary": [
{"speaker": "JOHNNY", "line": "She drops the beat β and drops EMINEM! IT'S OVER!"},
{"speaker": "NICK", "line": "Britney Spears wins the Deathmatch. Finally."}],
"video_prompt": "final spin counter, opponent flattened, victory pose, confetti"},
],
"winner": "A",
"winner_reason": "Britney's showmanship and speed turned every hit into a dance "
"finale β Eminem never found the off-beat.",
}
# Mirror the real backend's validated shape: keep the structured two-announcer
# lines in `commentary_lines`, and expose a flat "SPEAKER: line" caption string in
# `commentary` (what the UI renders and what burns in as subtitles).
for _beat in MOCK_FIGHTCARD["beats"]:
_lines = _beat["commentary"]
_beat["commentary_lines"] = _lines
_beat["commentary"] = " ".join(f"{ln['speaker']}: {ln['line']}" for ln in _lines)
_PALETTE = [
(210, 60, 70), (240, 160, 48), (64, 160, 200), (120, 90, 200), (60, 170, 110),
]
def _font(size: int):
for path in (
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
):
if os.path.exists(path):
return ImageFont.truetype(path, size)
try:
return ImageFont.load_default(size) # Pillow >= 10
except TypeError:
return ImageFont.load_default()
def _wrap(draw, text, font, max_w):
words, lines, cur = text.split(), [], ""
for w in words:
trial = f"{cur} {w}".strip()
if draw.textlength(trial, font=font) <= max_w:
cur = trial
else:
if cur:
lines.append(cur)
cur = w
if cur:
lines.append(cur)
return lines
def placeholder_keyframe(card: dict, beat: dict, size=(1024, 576)) -> Image.Image:
"""A labelled placeholder panel standing in for a FLUX render."""
w, h = size
idx = (beat["id"] - 1) % len(_PALETTE)
base = _PALETTE[idx]
img = Image.new("RGB", size, base)
draw = ImageDraw.Draw(img)
# Vignette-ish darkening band at the bottom for caption legibility.
draw.rectangle([0, h - 150, w, h], fill=(0, 0, 0))
title_f = _font(40)
body_f = _font(26)
small_f = _font(20)
a = card["fighter_a"]["name"]
b = card["fighter_b"]["name"]
draw.text((36, 28), f"ROUND {beat['id']}", font=_font(24), fill=(255, 255, 255))
draw.text((36, 60), f"{a} vs {b}", font=title_f, fill=(255, 255, 255))
for i, line in enumerate(_wrap(draw, beat["action"], body_f, w - 72)[:4]):
draw.text((36, 150 + i * 34), line, font=body_f, fill=(245, 245, 245))
for i, line in enumerate(_wrap(draw, beat["commentary"], small_f, w - 72)[:3]):
draw.text((36, h - 140 + i * 26), line, font=small_f, fill=(255, 220, 120))
draw.text((w - 150, 28), "MOCK", font=_font(28), fill=(255, 255, 255))
return img
def placeholder_reel(card: dict, size=(1024, 576)) -> list[Image.Image]:
return [placeholder_keyframe(card, beat, size) for beat in card["beats"]]
def mock_fight_video(frames: list[Image.Image], seconds_per_beat: float = 1.6,
fps: int = 24) -> str:
"""Stitch keyframes into an MP4 β the mock stand-in for the LTX fight video.
Commentary is already drawn onto each placeholder frame, so this mirrors the
real Stage-3 output type (an MP4 for gr.Video) without a GPU. Real mode swaps
in the chained, caption-burned clip from the deathmatch-video service.
"""
import imageio.v2 as imageio
import numpy as np
fd, path = tempfile.mkstemp(prefix="deathmatch_mock_", suffix=".mp4")
os.close(fd)
reps = max(1, int(seconds_per_beat * fps))
writer = imageio.get_writer(path, fps=fps, codec="libx264",
macro_block_size=8)
try:
for f in frames:
arr = np.asarray(f.convert("RGB"))
for _ in range(reps):
writer.append_data(arr)
finally:
writer.close()
return path
|