irkan commited on
Commit
e871829
Β·
verified Β·
1 Parent(s): 9a53105

Deploy deathmatch from d218214

Browse files
.gitattributes ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ examples/britney.jpg filter=lfs diff=lfs merge=lfs -text
2
+ examples/musk.jpg filter=lfs diff=lfs merge=lfs -text
3
+ examples/scarlett.jpg filter=lfs diff=lfs merge=lfs -text
4
+ examples/zuckerberg.jpg filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -6,35 +6,102 @@ colorTo: yellow
6
  sdk: gradio
7
  sdk_version: "6.0.0"
8
  app_file: app.py
9
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- # Celebrity Deathmatch
 
 
 
 
 
13
 
14
- Upload two photos, and an AI ring director books an MTV-style claymation brawl:
15
- a fight script with round-by-round announcer commentary, a rendered keyframe
16
- reel, a declared winner β€” and an optional **Animate** button that chains it into
17
- one continuous fight video with the commentary burned in as captions.
18
 
19
- > ⚠ **Parody.** All visuals are AI-generated claymation caricatures of public
20
- > figures, for comedic effect. Not real.
 
 
21
 
22
- ## Models
 
 
23
 
24
- | Stage | Role | Model | GPU |
25
- |------|------|-------|-----|
26
- | 1 | Fight director (reads both photos) | MiniCPM-V-2_6 (OpenBMB) | A10G |
27
- | 2 | Claymation keyframes | FLUX.1-schnell (BFL) | L40S |
28
- | 3 | Image→video fight clip (opt-in) | LTX-Video (Lightricks) | H100 |
29
 
30
- ## Architecture
31
 
32
- - **Frontend:** this Gradio Space.
33
- - **Backend:** Modal GPU app `deathmatch` β€” set `DEATHMATCH_API_URL` (the deploy
34
- pipeline does this automatically).
35
- - **Mock mode:** run locally with `DEATHMATCH_MOCK=1` to exercise the full UX on
36
- CPU (canned fight, placeholder frames, GIF stand-in for the video).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  ```sh
39
  DEATHMATCH_MOCK=1 python app.py
40
  ```
 
 
 
 
 
 
6
  sdk: gradio
7
  sdk_version: "6.0.0"
8
  app_file: app.py
9
+ pinned: true
10
+ short_description: Two photos in, a claymation celebrity death match out
11
+ tags:
12
+ - thousand-token-wood
13
+ - off-brand
14
+ - best-demo
15
+ - text-to-video
16
+ - image-generation
17
+ - text-to-speech
18
+ - gradio
19
+ - modal
20
+ models:
21
+ - openbmb/MiniCPM-V-2_6
22
+ - black-forest-labs/FLUX.1-schnell
23
+ - Lightricks/LTX-Video
24
+ - Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign
25
  ---
26
 
27
+ ![Track: Thousand Token Wood](https://img.shields.io/badge/track-Thousand%20Token%20Wood-7c3aed)
28
+ ![Badge: Off Brand](https://img.shields.io/badge/badge-Off%20Brand-e0303c)
29
+ ![Badge: Best Demo](https://img.shields.io/badge/badge-Best%20Demo-f2b030)
30
+ ![Models < 32B](https://img.shields.io/badge/all%20models-%3C32B-2ea44f)
31
+ ![Gradio 6](https://img.shields.io/badge/Gradio-6.x-orange)
32
+ ![Backend: Modal](https://img.shields.io/badge/backend-Modal%20GPU-0b6bcb)
33
 
34
+ # πŸ₯Š Celebrity Deathmatch
 
 
 
35
 
36
+ **Upload two photos. Our AI ring director books the brawl** β€” a claymation fight
37
+ script, a rendered keyframe reel, a declared winner β€” then turns it into one
38
+ continuous fight video with **two ring announcers screaming over the action** and
39
+ the crowd going wild.
40
 
41
+ It's MTV's *Celebrity Deathmatch* as an AI-native toy: pure spectacle, zero
42
+ practical value, maximum fun. (That's the [Thousand Token Wood](#) track in one
43
+ sentence.)
44
 
45
+ > ⚠️ **Parody.** Every visual is an AI-generated claymation **caricature** of a
46
+ > public figure, for comedic effect. Not real. No real people were harmed.
 
 
 
47
 
48
+ ## ▢️ See it in action
49
 
50
+ - **Demo video:** _TODO β€” paste your YouTube / uploaded video link here_
51
+ - **Social post:** _TODO β€” paste your launch post link here_
52
+
53
+ ## Why it's worth a look
54
+
55
+ - **πŸŽ™οΈ Two-announcer voiceover, not a silent clip.** Every beat is called by
56
+ **Nick** (dry, sarcastic) and **Johnny** (loud, over-excited) β€” two *designed*
57
+ voices from Qwen3-TTS VoiceDesign β€” mixed over a bell, a crowd murmur bed, and a
58
+ winner roar. The fight has a soundtrack, like the real show.
59
+ - **🎨 Off-brand UI.** No default Gradio look: a custom claymation-fight art
60
+ direction β€” Anton display type, fire-and-clay palette, tale-of-the-tape stat
61
+ bars, animated winner banner.
62
+ - **🧱 Real caricatures, real stakes.** MiniCPM *reads both photos*, invents a
63
+ fighting persona, signature move, and stat line for each, then choreographs a
64
+ 5-beat arc and picks a winner.
65
+
66
+ ## How it works
67
+
68
+ A four-model pipeline β€” **every model under the hackathon's 32B cap**:
69
+
70
+ | Stage | Role | Model | Params |
71
+ |------|------|-------|--------|
72
+ | 1 | Fight director β€” reads **both** photos β†’ fight card JSON | **MiniCPM-V-2_6** (OpenBMB) | 8B |
73
+ | 2 | Claymation keyframe reel | **FLUX.1-schnell** (BFL) | 12B |
74
+ | 3 | Keyframes β†’ continuous fight video (opt-in) | **LTX-Video** (Lightricks) | 2B |
75
+ | 3 | Two-announcer voiceover | **Qwen3-TTS VoiceDesign** (Qwen) | 2B |
76
+
77
+ ```
78
+ photo A + photo B
79
+ └─▢ Stage 1 MiniCPM-V-2_6 β†’ fight card (fighters, 5 beats, 2-announcer commentary, winner)
80
+ └─▢ Stage 2 FLUX.1-schnell β†’ 5 claymation keyframes
81
+ └─▢ Stage 3 (Animate) LTX-Video β†’ chained clips
82
+ + Qwen3-TTS β†’ Nick/Johnny voiceover + crowd SFX + burned-in captions
83
+ β†’ one MP4
84
+ ```
85
+
86
+ The reel (Stages 1–2) is the fast default; **Animate** (Stage 3) is opt-in
87
+ because it's the GPU-heavy step.
88
+
89
+ ## Tech
90
+
91
+ - **Frontend:** this Gradio 6 Space (custom CSS, no template look).
92
+ - **Backend:** two Modal GPU apps β€” `deathmatch` (MiniCPM A10G + FLUX L40S +
93
+ ASGI gateway) and `deathmatch-video` (ComfyUI + LTX on H100, Qwen3-TTS on A10G).
94
+ - **Audio/video post:** pure `ffmpeg` β€” xfade stitch, fit-to-beat TTS mixing,
95
+ synthesized crowd SFX, burned-in captions.
96
+ - Wired to the backend via `DEATHMATCH_API_URL` (set automatically on deploy).
97
+
98
+ ## Run it locally (no GPU)
99
 
100
  ```sh
101
  DEATHMATCH_MOCK=1 python app.py
102
  ```
103
+
104
+ Mock mode exercises the **entire UX** on CPU β€” canned fight card, placeholder
105
+ keyframe reel, and a stand-in fight video β€” so you can see the whole flow without
106
+ burning a single GPU minute.
107
+ </content>
app.py CHANGED
@@ -111,6 +111,51 @@ button.primary, .dm-primary button {
111
  }
112
  label, .block label { color: var(--dm-muted) !important; font-family:'JetBrains Mono',monospace !important;
113
  font-size: 11px !important; letter-spacing: 0.07em !important; text-transform: uppercase !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  """
115
 
116
 
@@ -311,12 +356,39 @@ with gr.Blocks(title="Celebrity Deathmatch") as demo:
311
  )
312
  export_btn.click(export_card, [card_state], [export_file])
313
 
314
- demo_a = os.path.join(os.path.dirname(__file__), "examples", "fighter_a.png")
315
- demo_b = os.path.join(os.path.dirname(__file__), "examples", "fighter_b.png")
316
- if os.path.exists(demo_a) and os.path.exists(demo_b):
317
- gr.Examples(examples=[[demo_a, demo_b, "", ARENAS[0], STYLES[0]]],
318
- inputs=[img_a, img_b, storyline, arena, style],
319
- label="Try a demo matchup")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
 
322
  if __name__ == "__main__":
 
111
  }
112
  label, .block label { color: var(--dm-muted) !important; font-family:'JetBrains Mono',monospace !important;
113
  font-size: 11px !important; letter-spacing: 0.07em !important; text-transform: uppercase !important; }
114
+
115
+ /* ───────── de-Gradio: hide framework chrome, theme the native controls so the
116
+ Space stops looking like a Gradio template (Off-Brand) ───────── */
117
+ footer, .gradio-container footer { display: none !important; }
118
+ .gradio-container { border: none !important; box-shadow: none !important; }
119
+ /* Let the page background show through default component panels */
120
+ .block, .form, .gr-group, .gr-box { background: transparent !important; border: none !important; box-shadow: none !important; }
121
+
122
+ /* Tabs β†’ claymation chips, not default Gradio tabs */
123
+ .tab-nav, .tabs > .tab-nav { border-bottom: none !important; gap: 8px !important; margin-bottom: 14px !important; }
124
+ .tab-nav button {
125
+ background: rgba(10,6,8,0.6) !important; color: var(--dm-muted) !important;
126
+ border: 1px solid rgba(255,255,255,0.08) !important; border-radius: 12px !important;
127
+ font-family: 'Anton', sans-serif !important; letter-spacing: 0.05em !important;
128
+ text-transform: uppercase !important; padding: 8px 16px !important;
129
+ }
130
+ .tab-nav button.selected {
131
+ color: #1a0608 !important; border-color: transparent !important;
132
+ background: linear-gradient(135deg, var(--dm-gold), var(--dm-red)) !important;
133
+ }
134
+
135
+ /* Native inputs β†’ dark and on-theme */
136
+ input[type=text], textarea, select, .gr-input, .wrap input, .multiselect input {
137
+ background: rgba(10,6,8,0.66) !important; color: var(--dm-text) !important;
138
+ border: 1px solid rgba(255,255,255,0.12) !important; border-radius: 12px !important;
139
+ }
140
+ input::placeholder, textarea::placeholder { color: rgba(176,144,136,0.6) !important; }
141
+
142
+ /* Upload dropzones β†’ dashed gold, matching the disclaimer band */
143
+ .image-container, [data-testid="image"], .gr-image, .upload-container {
144
+ border: 1px dashed rgba(242,176,48,0.4) !important; border-radius: 16px !important;
145
+ background: rgba(10,6,8,0.4) !important;
146
+ }
147
+
148
+ /* Secondary / download buttons β†’ de-orange */
149
+ button.secondary, .secondary > button {
150
+ background: rgba(255,255,255,0.06) !important; color: var(--dm-text) !important;
151
+ border: 1px solid rgba(255,255,255,0.14) !important; border-radius: 999px !important;
152
+ font-family: 'JetBrains Mono', monospace !important; text-transform: uppercase !important;
153
+ letter-spacing: 0.06em !important; font-size: 12px !important;
154
+ }
155
+
156
+ /* Kill the orange Gradio accent + focus ring */
157
+ .gradio-container { --color-accent: var(--dm-gold) !important; --color-accent-soft: rgba(242,176,48,0.2) !important; }
158
+ :focus-visible { outline: 1px solid var(--dm-gold) !important; box-shadow: none !important; }
159
  """
160
 
161
 
 
356
  )
357
  export_btn.click(export_card, [card_state], [export_file])
358
 
359
+ # Pre-loaded celebrity matchups (parody). Each entry pre-fills both photos +
360
+ # names + arena. A matchup only appears once BOTH of its photos are present in
361
+ # ui/examples/ (drop in legally-licensed images β€” see examples/README.md), so
362
+ # this is safe to ship even before the photos are added.
363
+ _EX_DIR = os.path.join(os.path.dirname(__file__), "examples")
364
+
365
+ def _ex_photo(slug):
366
+ for ext in (".jpg", ".jpeg", ".png", ".webp"):
367
+ p = os.path.join(_EX_DIR, slug + ext)
368
+ if os.path.exists(p):
369
+ return p
370
+ return None
371
+
372
+ # (slug_a, name_a, slug_b, name_b, arena)
373
+ EXAMPLE_MATCHUPS = [
374
+ ("britney", "Britney Spears", "eminem", "Eminem", "Classic Deathmatch ring"),
375
+ ("musk", "Elon Musk", "zuckerberg", "Mark Zuckerberg", "Neon Tokyo street"),
376
+ ("keanu", "Keanu Reeves", "scarlett", "Scarlett Johansson", "Rooftop at night"),
377
+ ]
378
+ _examples = []
379
+ for a_slug, a_name, b_slug, b_name, ex_arena in EXAMPLE_MATCHUPS:
380
+ pa, pb = _ex_photo(a_slug), _ex_photo(b_slug)
381
+ if pa and pb:
382
+ arena_val = ex_arena if ex_arena in ARENAS else ARENAS[0]
383
+ _examples.append([pa, pb, "", arena_val, STYLES[0], a_name, b_name])
384
+
385
+ if _examples:
386
+ gr.Examples(
387
+ examples=_examples,
388
+ inputs=[img_a, img_b, storyline, arena, style, name_a, name_b],
389
+ label="⚑ Try a celebrity matchup (parody)",
390
+ examples_per_page=3,
391
+ )
392
 
393
 
394
  if __name__ == "__main__":
examples/README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Example matchup photos
2
+
3
+ Drop fighter photos here and they appear automatically as one-click **"Try a
4
+ celebrity matchup"** examples on the Tale of the Tape tab. A matchup only shows
5
+ up once **both** of its photos exist, so missing files are simply skipped.
6
+
7
+ ## Filenames the app looks for
8
+
9
+ Any of `.jpg` / `.jpeg` / `.png` / `.webp`:
10
+
11
+ | Matchup | Fighter A file | Fighter B file |
12
+ |---|---|---|
13
+ | Britney vs. Eminem | `britney.jpg` | `eminem.jpg` |
14
+ | Elon Musk vs. Mark Zuckerberg | `musk.jpg` | `zuckerberg.jpg` |
15
+ | Keanu Reeves vs. Scarlett Johansson | `keanu.jpg` | `scarlett.jpg` |
16
+
17
+ (The display names + arena are pre-filled by the app β€” see `EXAMPLE_MATCHUPS`
18
+ in `ui/app.py`. To add a new matchup, add a row there and drop in two photos.)
19
+
20
+ ## Photo tips
21
+
22
+ - Roughly **square**, **face/head-and-shoulders**, front-facing, ~512px+.
23
+ - A clear, well-lit single face gives the best clay caricature.
24
+
25
+ ## ⚠️ Licensing β€” please read
26
+
27
+ These photos get **committed to a public Hugging Face Space**, so use images you
28
+ have the right to redistribute:
29
+
30
+ - **Wikimedia Commons** is the easiest source β€” most of these public figures have
31
+ **CC BY / CC BY-SA** or public-domain portraits. Download from there and keep
32
+ the attribution.
33
+ - Avoid press/agency/paparazzi shots (Getty, etc.) β€” those are copyrighted and
34
+ not yours to redistribute.
35
+ - The app's on-screen "parody / AI caricature" disclaimer covers the *generated*
36
+ output, **not** redistribution of a copyrighted source photo. Source matters.
37
+
38
+ If in doubt, use a CC-licensed Wikimedia Commons portrait (and note the credit).
examples/britney.jpg ADDED

Git LFS Details

  • SHA256: b83bedcc9112a47225d9a33812e1fa7cf8cce4e930428c624131a752f26edc4a
  • Pointer size: 131 Bytes
  • Size of remote file: 158 kB
examples/eminem.jpg ADDED
examples/keanu.jpg ADDED
examples/musk.jpg ADDED

Git LFS Details

  • SHA256: 456a3df4fceb77a05ce0eba0b1ea645b5f521557018ace4d33087026c472e168
  • Pointer size: 131 Bytes
  • Size of remote file: 196 kB
examples/scarlett.jpg ADDED

Git LFS Details

  • SHA256: dcf19e5494127cf62a4a417832093338100c73fbd9c1ab6ee50ad60d5997d50b
  • Pointer size: 131 Bytes
  • Size of remote file: 273 kB
examples/zuckerberg.jpg ADDED

Git LFS Details

  • SHA256: 64688deb0926467796905bd67cd0567683c38687e09b61657c148d858a45f7c4
  • Pointer size: 131 Bytes
  • Size of remote file: 307 kB