ducks-happen / app.py
sarahsl-prog's picture
Add duck generator app
7a91ae0
Raw
History Blame Contribute Delete
7.98 kB
import gradio as gr
import spaces
import torch
from diffusers import FluxPipeline
import random
import time
# ── Prompt Ingredients ────────────────────────────────────────────────────────
SETTINGS = [
"outer space", "a medieval tavern", "the bottom of the ocean",
"a Tokyo street at night", "a Victorian drawing room",
"an Ancient Egyptian tomb", "a pirate ship deck", "a haunted forest",
"the Arctic tundra", "a Paris sidewalk cafΓ©", "a Wild West saloon",
"a cyberpunk alley", "a Renaissance fair", "ancient jungle temple ruins",
"a cloud kingdom", "a submarine interior", "a 1920s jazz club",
"the Roman Colosseum", "a dragon's lair", "an enchanted library",
"a Mars colony", "a cozy hobbit hole", "a floating sky island",
"a neon-lit casino", "sunken Atlantis", "a volcano crater rim",
"a hedge maze", "a moon base", "an interdimensional rift",
"a hot air balloon over the Alps",
]
OUTFITS = [
"a pirate costume", "Victorian mourning wear", "a hazmat suit",
"a beekeeper suit", "an astronaut suit", "Renaissance knight armor",
"wizard robes", "a cowboy hat and spurs", "samurai armor",
"a ballerina tutu", "a heavy metal band t-shirt",
"a royal crown and velvet cape", "a chef's hat and apron",
"a detective trench coat", "a superhero cape", "full scuba gear",
"a tuxedo", "a Hawaiian shirt", "a ninja outfit", "a disco jumpsuit",
"a graduation cap and gown", "a viking helmet",
"full plate armor", "a lab coat and goggles", "a pharaoh's headdress",
"a clown costume", "a judge's wig and robes",
]
MOODS = [
"looking deeply contemplative", "appearing extremely suspicious",
"seemingly thrilled beyond reason", "looking absolutely baffled",
"radiating unearned confidence", "looking mildly judgmental",
"appearing philosophical", "seeming utterly delighted",
"looking deeply unimpressed", "appearing to have seen too much",
"radiating chaotic energy", "looking inexplicably regal",
"seemingly plotting something", "looking profoundly unbothered",
"appearing heroic", "looking vaguely menacing",
"seeming emotionally unavailable", "radiating main character energy",
"looking like they own the place",
]
STYLES = [
"oil painting", "watercolor illustration", "photorealistic photograph",
"vintage postcard", "pencil sketch", "impressionist painting",
"children's book illustration", "art nouveau poster",
"cinematic still", "gouache illustration", "linocut print",
"ukiyo-e woodblock print", "stained glass window",
"renaissance portrait", "propaganda poster style",
]
MIN_WAIT = 45
MAX_WAIT = 120
def build_prompt():
setting = random.choice(SETTINGS)
outfit = random.choice(OUTFITS)
mood = random.choice(MOODS)
style = random.choice(STYLES)
prompt = (
f"a cute rubber duck wearing {outfit}, in {setting}, "
f"{mood}, {style}, highly detailed, charming, whimsical"
)
def clean(s):
return s.replace("a ", "").replace("an ", "").replace("the ", "")
caption = f"{clean(outfit).title()} Β· {clean(setting).title()}"
return prompt, caption
# ── Model ──────────────────────────────────────────────────────────────────────
pipe = FluxPipeline.from_pretrained(
"black-forest-labs/FLUX.1-schnell",
torch_dtype=torch.bfloat16,
)
@spaces.GPU(duration=60)
def generate_duck():
pipe.to("cuda")
prompt, caption = build_prompt()
image = pipe(
prompt,
num_inference_steps=4,
guidance_scale=0.0,
height=512,
width=512,
).images[0]
pipe.to("cpu")
torch.cuda.empty_cache()
return image, caption
# ── Timer Callback ─────────────────────────────────────────────────────────────
def tick(next_at, items):
now = time.time()
if now < next_at:
secs = int(next_at - now)
return next_at, items, f"⏳ Next duck in ~**{secs}s** ... probably", items
image, caption = generate_duck()
new_items = [(image, caption)] + (items or [])
new_items = new_items[:12]
next_t = now + random.uniform(MIN_WAIT, MAX_WAIT)
status = f"πŸ¦† A duck has appeared! Next one in ~{int(next_t - now)}s"
return next_t, new_items, status, new_items
# ── Styles ─────────────────────────────────────────────────────────────────────
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;600&display=swap');
body, .gradio-container {
background-color: #0d0d0d !important;
font-family: 'Nunito', sans-serif !important;
color: #e0e0e0 !important;
}
#title {
text-align: center;
font-family: 'Fredoka One', cursive !important;
font-size: 3.2rem !important;
color: #FFD700 !important;
text-shadow: 0 0 24px rgba(255,215,0,0.35), 0 2px 4px rgba(0,0,0,0.5);
margin-bottom: 2px !important;
line-height: 1.1;
}
#subtitle p {
text-align: center;
color: #888 !important;
font-size: 1rem !important;
font-style: italic;
margin-top: 2px !important;
}
#status-bar {
background: #141414;
border: 1px solid #2a2a2a;
border-radius: 10px;
padding: 8px 20px;
margin: 12px auto;
max-width: 480px;
text-align: center;
}
#status-bar p {
color: #FFD700 !important;
font-size: 0.9rem !important;
margin: 0 !important;
}
#duck-gallery {
margin-top: 8px;
}
#duck-gallery .grid-wrap {
background: transparent !important;
gap: 10px !important;
}
#duck-gallery .thumbnail-item {
border-radius: 12px !important;
overflow: hidden;
border: 2px solid #1e1e1e !important;
transition: border-color 0.25s ease, transform 0.25s ease;
}
#duck-gallery .thumbnail-item:hover {
border-color: #FFD700 !important;
transform: scale(1.02);
}
#duck-gallery .caption-label {
background: rgba(0,0,0,0.75) !important;
color: #FFD700 !important;
font-size: 0.75rem !important;
font-family: 'Nunito', sans-serif !important;
}
#footer-note p {
text-align: center;
color: #444;
font-size: 0.78rem;
margin-top: 16px;
}
footer { display: none !important; }
"""
# ── App ────────────────────────────────────────────────────────────────────────
with gr.Blocks(css=CSS, title="Ducks Happen πŸ¦†") as demo:
next_at = gr.State(value=time.time() + 8) # first duck in ~8s
items = gr.State(value=[])
gr.Markdown("# πŸ¦† Ducks Happen", elem_id="title")
gr.Markdown(
"*Rubber ducks materialize here. There is nothing you can do about it.*",
elem_id="subtitle",
)
status_md = gr.Markdown("⏳ Preparing the first duck...", elem_id="status-bar")
gallery = gr.Gallery(
label=None,
show_label=False,
columns=3,
rows=2,
object_fit="cover",
height="auto",
elem_id="duck-gallery",
)
gr.Markdown(
"<p>Built for the <a href='https://huggingface.co/build-small-hackathon' "
"style='color:#FFD700;'>Build Small Hackathon 2026</a> Β· "
"Powered by FLUX.1-schnell Β· πŸ¦†</p>",
elem_id="footer-note",
)
timer = gr.Timer(5)
timer.tick(
fn=tick,
inputs=[next_at, items],
outputs=[next_at, items, status_md, gallery],
concurrency_limit=1,
)
demo.launch()