Spaces:
Sleeping
Sleeping
File size: 6,147 Bytes
4a0bf67 86675c4 a6cf6ed 26f60d1 4a0bf67 86675c4 4a0bf67 26f60d1 4a0bf67 26f60d1 4a0bf67 86675c4 4a0bf67 86675c4 4a0bf67 189dd71 a6cf6ed 189dd71 a6cf6ed 30f861a 86675c4 a6cf6ed 189dd71 86675c4 d2d5c14 26f60d1 86675c4 26f60d1 a6cf6ed 26f60d1 a6cf6ed 26f60d1 86675c4 26f60d1 189dd71 4a0bf67 86675c4 189dd71 4a0bf67 cdc28b0 4a0bf67 26f60d1 4a0bf67 26f60d1 4a0bf67 26f60d1 4a0bf67 26f60d1 4a0bf67 cdc28b0 | 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 | import gradio as gr
import os
from PIL import Image
import time
from huggingface_hub import InferenceClient
import imageio
import numpy as np
# Using 'black-forest-labs/FLUX.1-schnell' for fast, reliable image generation.
# Can be overridden via environment variables if needed.
T2I_SPACE = os.environ.get("T2I_SPACE", "black-forest-labs/FLUX.1-schnell")
def generate_exercise_video(exercise_name, output_format):
if not exercise_name or len(exercise_name.strip()) == 0:
return None, None, gr.Image(), gr.Video()
# Construct an optimized storyboard prompt
prompt = (
f"A 3-panel horizontal storyboard showing a fit person performing the exercise: {exercise_name}. "
"Panel 1: starting position, Panel 2: midpoint action, Panel 3: starting position. "
"Three side-by-side horizontal panels, clean white background, high quality, consistent character."
)
try:
hf_token = os.environ.get("HF_TOKEN")
print(f"Connecting to serverless Inference Client for {T2I_SPACE}...")
client = InferenceClient(T2I_SPACE, token=hf_token)
print("Generating storyboard image via serverless API...")
img = client.text_to_image(prompt, width=1024, height=512)
w, h = img.size
print(f"Storyboard generated with size: {w}x{h}. Processing frames...")
# Split into 3 horizontal frames
frame_width = w // 3
frames = []
for i in range(3):
box = (i * frame_width, 0, (i + 1) * frame_width, h)
frame = img.crop(box)
frames.append(frame)
# Compile into a looping frame sequence (Frame 1 -> Frame 2 -> Frame 3 -> Frame 2)
loop_frames = [frames[0], frames[1], frames[2], frames[1]]
# Ensure temporary directory exists
tmp_dir = os.path.join(os.getcwd(), "tmp_animations")
os.makedirs(tmp_dir, exist_ok=True)
timestamp = int(time.time())
# 1. Save as Animated GIF
gif_path = os.path.join(tmp_dir, f"exercise_animation_{timestamp}.gif")
loop_frames[0].save(
gif_path,
save_all=True,
append_images=loop_frames[1:],
duration=500, # 500ms per frame
loop=0
)
print(f"Successfully saved looping GIF to: {gif_path}")
# 2. Save as MP4 Video
mp4_path = os.path.join(tmp_dir, f"exercise_animation_{timestamp}.mp4")
# Resize to even dimensions for standard video codec compatibility
w_even = (frame_width // 2) * 2
h_even = (h // 2) * 2
processed_video_frames = []
for f in loop_frames:
if f.size != (w_even, h_even):
f = f.resize((w_even, h_even), Image.Resampling.LANCZOS)
processed_video_frames.append(np.array(f))
# Write MP4 (fps=2 matches 500ms duration per frame)
# macro_block_size=None keeps the exact resized dimensions (e.g. 340x512)
writer = imageio.get_writer(mp4_path, fps=2, macro_block_size=None)
for frame in processed_video_frames:
writer.append_data(frame)
writer.close()
print(f"Successfully saved looping MP4 to: {mp4_path}")
# Return files and visibility configurations depending on chosen format
if output_format == "Animated GIF":
return (
gif_path,
mp4_path,
gr.Image(visible=True, value=gif_path),
gr.Video(visible=False, value=None)
)
else:
return (
gif_path,
mp4_path,
gr.Image(visible=False, value=None),
gr.Video(visible=True, value=mp4_path, autoplay=True, loop=True)
)
except Exception as e:
print(f"Error generating animation: {e}")
raise gr.Error(f"Failed to animate exercise. The API returned an error: {e}")
# Build the Gradio Interface
with gr.Blocks() as demo:
gr.Markdown("# 🏋️ Exercise Animator")
gr.Markdown("Type the name of an exercise to see an AI-generated animation of how to perform it.")
# Store file paths so switching format doesn't require regeneration
gif_state = gr.State(value=None)
mp4_state = gr.State(value=None)
with gr.Row():
with gr.Column():
exercise_input = gr.Textbox(
label="Exercise Name",
placeholder="e.g., Jumping Jacks, Squats, Pushups...",
lines=1
)
format_choice = gr.Radio(
choices=["Animated GIF", "Video (MP4)"],
value="Animated GIF",
label="Output Format"
)
submit_btn = gr.Button("Animate", variant="primary")
with gr.Column():
video_output_image = gr.Image(label="Generated Animation (GIF)", type="filepath", visible=True)
video_output_video = gr.Video(label="Generated Animation (Video)", visible=False)
submit_btn.click(
fn=generate_exercise_video,
inputs=[exercise_input, format_choice],
outputs=[gif_state, mp4_state, video_output_image, video_output_video]
)
def toggle_format(choice, gif_path, mp4_path):
if choice == "Animated GIF":
return (
gr.Image(visible=True, value=gif_path),
gr.Video(visible=False, value=None)
)
else:
return (
gr.Image(visible=False, value=None),
gr.Video(visible=True, value=mp4_path, autoplay=True, loop=True)
)
format_choice.change(
fn=toggle_format,
inputs=[format_choice, gif_state, mp4_state],
outputs=[video_output_image, video_output_video]
)
gr.Examples(
examples=["Jumping Jacks", "Squats", "Pushups", "Burpees", "Yoga Downward Dog"],
inputs=[exercise_input]
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft(), server_name="0.0.0.0")
|