Spaces:
Running
Running
File size: 9,054 Bytes
64e16be e9ffffc 00e533f e9ffffc 00e533f e9ffffc 64e16be e9ffffc 00e533f e9ffffc 64e16be e9ffffc 64e16be e9ffffc be6c0ec 64e16be 9dbfc4b 64e16be 9dbfc4b 64e16be 00e533f 2c58b33 64e16be 00e533f 64e16be 7802a19 64e16be e9ffffc 64e16be 7802a19 64e16be e9ffffc 64e16be e9ffffc 64e16be 83aef45 |
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 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
import gradio as gr
import os
import base64
from io import BytesIO
from PIL import Image
import tempfile
from tools import generate_pixel_character, animate_pixel_character, extract_sprite_frames
# --- Helper Functions for Gradio Logic ---
def process_generate_sprite(prompt, ref_img):
"""
Generates a static 2D sprite character based on a text description in any art style.
Args:
prompt: Description of the character and style (e.g., "A cute cat wizard, cartoon style" or "anime cat hero").
ref_img: Optional reference image to influence style.
Returns:
The generated sprite image and its base64 encoding.
"""
try:
ref_b64 = None
if ref_img is not None:
# Convert numpy array or PIL image to base64
if isinstance(ref_img, str): # path
with open(ref_img, "rb") as f:
ref_b64 = base64.b64encode(f.read()).decode('utf-8')
elif hasattr(ref_img, "save"): # PIL Image
buffered = BytesIO()
ref_img.save(buffered, format="PNG")
ref_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
b64_result = generate_pixel_character(prompt, ref_b64)
# Convert back to PIL for display
img_data = base64.b64decode(b64_result)
return Image.open(BytesIO(img_data)), b64_result
except Exception as e:
raise gr.Error(str(e))
def process_animate_sprite(sprite_img, animation_type, extra_prompt):
"""
Animates a static 2D sprite using Google's Veo model.
Args:
sprite_img: The input static sprite image.
animation_type: Type of animation - one of "idle", "walk", "run", "jump".
extra_prompt: Optional additional instructions for the motion.
Returns:
The generated animation video path and its base64 encoding.
"""
try:
if sprite_img is None:
raise ValueError("Please provide a sprite image first.")
# Convert input image to base64
sprite_b64 = None
if isinstance(sprite_img, str): # path provided by Gradio example or upload
with open(sprite_img, "rb") as f:
sprite_b64 = base64.b64encode(f.read()).decode('utf-8')
elif hasattr(sprite_img, "save"): # PIL Image
buffered = BytesIO()
sprite_img.save(buffered, format="PNG")
sprite_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
elif isinstance(sprite_img, tuple): # Sometimes Gradio returns (path, meta)
# Handle other formats if necessary
pass
# If sprite_b64 is still None (e.g. numpy array), try to convert
if sprite_b64 is None:
# Assuming numpy array -> PIL -> Base64
im = Image.fromarray(sprite_img)
buffered = BytesIO()
im.save(buffered, format="PNG")
sprite_b64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
video_b64 = animate_pixel_character(sprite_b64, animation_type, extra_prompt)
# Save to temp file for Gradio to display
video_bytes = base64.b64decode(video_b64)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as f:
f.write(video_bytes)
video_path = f.name
return video_path, video_b64
except Exception as e:
raise gr.Error(str(e))
def process_extract_frames(video_file, fps):
"""
Extracts frames from an MP4 video animation and returns them as individual images and a ZIP file.
Args:
video_file: The input MP4 video file path.
fps: Frames per second to extract (default 8).
Returns:
A gallery of extracted frames and a ZIP file containing all frames.
"""
try:
if video_file is None:
raise ValueError("Please upload a video file.")
# Read video file to base64
with open(video_file, "rb") as f:
video_b64 = base64.b64encode(f.read()).decode('utf-8')
zip_b64, frames_b64 = extract_sprite_frames(video_b64, fps)
# Save zip to temp file
with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as f:
f.write(base64.b64decode(zip_b64))
zip_path = f.name
# Convert frames to gallery format (list of paths or PIL images)
gallery_images = []
for fb64 in frames_b64:
img_data = base64.b64decode(fb64)
gallery_images.append(Image.open(BytesIO(img_data)))
return gallery_images, zip_path
except Exception as e:
raise gr.Error(str(e))
# --- Gradio UI Layout ---
with gr.Blocks(title="GameSmith AI - Game Asset Studio") as demo:
gr.Markdown(
"""
# 🎮 GameSmith AI
### The Intelligent Game Asset Studio
Generate, animate, and export 2D game sprites in any art style using Google Gemini & Veo.
*Built for the Hugging Face MCP 1st Birthday Hackathon.*
"""
)
with gr.Tab("1. Generate Sprite"):
with gr.Row():
with gr.Column():
prompt_input = gr.Textbox(
label="Character Description",
placeholder="A cute cat wearing a wizard hat, side view... (cartoon, anime, pixel art, etc.)",
lines=3
)
ref_input = gr.Image(label="Style Reference (Optional)", type="pil")
gen_btn = gr.Button("Generate Sprite", variant="primary")
with gr.Column():
result_image = gr.Image(label="Generated Sprite", type="pil", interactive=False)
# Hidden state to pass base64 to next tab if needed
sprite_b64_state = gr.State()
gen_btn.click(
process_generate_sprite,
inputs=[prompt_input, ref_input],
outputs=[result_image, sprite_b64_state],
api_name="generate_pixel_character"
)
with gr.Tab("2. Animate"):
with gr.Row():
with gr.Column():
# Allow user to use generated image or upload new
anim_input_image = gr.Image(label="Input Sprite", type="pil")
anim_type = gr.Dropdown(
choices=["idle", "walk", "run", "jump"],
value="idle",
label="Animation Type"
)
extra_anim_prompt = gr.Textbox(
label="Motion Tweaks (Optional)",
placeholder="Make it bounce more..."
)
anim_btn = gr.Button("Animate", variant="primary")
with gr.Column():
result_video = gr.Video(label="Generated Animation", interactive=False)
video_b64_state = gr.State()
# Link previous tab result to this input
result_image.change(
lambda x: x,
inputs=[result_image],
outputs=[anim_input_image]
)
anim_btn.click(
process_animate_sprite,
inputs=[anim_input_image, anim_type, extra_anim_prompt],
outputs=[result_video, video_b64_state],
api_name="animate_pixel_character"
)
with gr.Tab("3. Extract Frames"):
with gr.Row():
with gr.Column():
# Allow user to use generated video or upload new
extract_input_video = gr.Video(label="Input Animation")
fps_slider = gr.Slider(minimum=4, maximum=24, value=8, step=1, label="FPS")
extract_btn = gr.Button("Extract Frames", variant="primary")
with gr.Column():
frames_gallery = gr.Gallery(label="Sprite Sheet Frames")
download_zip = gr.File(label="Download Sprite Sheet (ZIP)")
# Link previous tab result
result_video.change(
lambda x: x,
inputs=[result_video],
outputs=[extract_input_video]
)
extract_btn.click(
process_extract_frames,
inputs=[extract_input_video, fps_slider],
outputs=[frames_gallery, download_zip],
api_name="extract_sprite_frames"
)
gr.Markdown("---")
gr.Markdown("### 🤖 Model Context Protocol (MCP)")
gr.Markdown(
"""
This app doubles as an MCP Server! Connect it to Claude or Cursor to generate assets directly in your chat.
**Tools Exposed:**
- `generate_pixel_character(prompt)`
- `animate_pixel_character(sprite_b64, animation_type)`
- `extract_sprite_frames(video_b64)`
"""
)
if __name__ == "__main__":
# Launch the app
# MCP server is auto-enabled via GRADIO_MCP_SERVER env var or newer Gradio versions
demo.launch()
|