jit / app.py
rahul7star's picture
Create app.py
425f87f verified
Raw History Blame Contribute Delete
9.32 kB
"""Hugging Face Spaces CPU-only image studio."""
import gc
import os
import random
import tempfile
from pathlib import Path
os.environ['CUDA_VISIBLE_DEVICES'] = ''
os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1'
import gradio as gr
import numpy as np
import torch
from PIL import Image
# CPU-compatible Diffusers backend. WeeLLM itself is GPU-oriented.
MODELS = {
'Tiny Stable Diffusion (fast test, low quality)': 'diffusers/tiny-stable-diffusion-torch',
'Stable Diffusion 1.5 (slow, higher quality)': 'stable-diffusion-v1-5/stable-diffusion-v1-5',
}
pipe = None
loaded_id = None
def load_model(model_name, progress):
global pipe, loaded_id
model_id = MODELS[model_name]
if pipe is not None and loaded_id == model_id:
return pipe
progress(0.03, desc='Loading model (first run downloads weights)')
pipe = None
loaded_id = None
gc.collect()
from diffusers import StableDiffusionPipeline
new_pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float32,
safety_checker=None,
requires_safety_checker=False,
)
new_pipe = new_pipe.to('cpu')
new_pipe.enable_attention_slicing()
pipe = new_pipe
loaded_id = model_id
return pipe
def render_image(model_name, prompt, negative, width, height, steps, guidance, seed, progress):
if not prompt or not prompt.strip():
raise gr.Error('Please enter a prompt.')
seed = int(seed) if seed is not None and int(seed) >= 0 else random.randint(0, 2**31 - 1)
model = load_model(model_name, progress)
progress(0.2, desc='Generating image on CPU; this may take several minutes')
with torch.inference_mode():
result = model(
prompt=prompt.strip(),
negative_prompt=negative or '',
width=int(width),
height=int(height),
num_inference_steps=int(steps),
guidance_scale=float(guidance),
generator=torch.Generator(device='cpu').manual_seed(seed),
)
return result.images[0], seed
def generate_image(model_name, prompt, negative, width, height, steps, guidance, seed, progress=gr.Progress()):
try:
img, used_seed = render_image(model_name, prompt, negative, width, height, steps, guidance, seed, progress)
progress(1, desc='Done')
return img, f'Image ready · seed {used_seed}'
except Exception as exc:
raise gr.Error(f'{type(exc).__name__}: {exc}') from exc
def animate_image(img, seconds, fps, zoom):
import imageio.v2 as imageio
if img is None:
raise gr.Error('Upload an image or generate one first.')
img = Image.fromarray(np.asarray(img.convert('RGB')))
width, height = img.size
frames = max(2, round(float(seconds) * int(fps)))
output = Path(tempfile.mkdtemp(prefix='weellm_studio_')) / 'animation.mp4'
try:
writer = imageio.get_writer(str(output), fps=int(fps), codec='libx264', quality=7, macro_block_size=16)
with writer:
for index in range(frames):
t = index / max(1, frames - 1)
scale = 1 + (float(zoom) - 1) * t
cw, ch = max(1, int(width / scale)), max(1, int(height / scale))
x0 = (width - cw) // 2
y0 = (height - ch) // 2
frame = img.crop((x0, y0, x0 + cw, y0 + ch)).resize((width, height), Image.Resampling.LANCZOS)
writer.append_data(np.asarray(frame))
except Exception as exc:
raise gr.Error(f'Video encoding failed: {exc}') from exc
return str(output), 'MP4 created using camera motion (not AI-generated video).'
def generate_video(model_name, prompt, negative, width, height, steps, guidance, seed, seconds, fps, zoom, progress=gr.Progress()):
try:
img, used_seed = render_image(model_name, prompt, negative, width, height, steps, guidance, seed, progress)
progress(0.85, desc='Encoding MP4')
path, status = animate_image(img, seconds, fps, zoom)
progress(1, desc='Done')
return img, path, f'{status} · seed {used_seed}'
except Exception as exc:
raise gr.Error(f'{type(exc).__name__}: {exc}') from exc
CSS = '''
.gradio-container {max-width: 1220px !important; margin: auto !important;}
.hero {padding: 18px 6px 24px;}
.hero h1 {font-size: 2.4rem; font-weight: 800; letter-spacing: -.04em; margin-bottom: 6px;}
.hero p {opacity: .78; font-size: 1.05rem;}
'''
with gr.Blocks(title='WeeLLM CPU Studio', theme=gr.themes.Soft(primary_hue='indigo'), css=CSS) as demo:
gr.HTML('<div class="hero"><h1>✦ WeeLLM CPU Studio</h1><p>Image creation and animated video · Hugging Face CPU Spaces · No GPU required</p></div>')
gr.Markdown('**Backend:** CPU-compatible Diffusers. This deployment does **not** run WeeLLM inference, which may require CUDA. The video feature animates a still image rather than generating motion with a video diffusion model.')
with gr.Tabs():
with gr.Tab('✦ Text to image'):
with gr.Row():
with gr.Column(scale=2):
model = gr.Dropdown(list(MODELS), value=list(MODELS)[0], label='Image model')
prompt = gr.Textbox(lines=4, label='Prompt', placeholder='A cozy cabin in a snowy forest, cinematic lighting...')
negative = gr.Textbox(label='Negative prompt', value='blurry, distorted, low quality')
with gr.Accordion('Settings', open=True):
with gr.Row():
width = gr.Dropdown([256, 384, 512], value=256, label='Width')
height = gr.Dropdown([256, 384, 512], value=256, label='Height')
steps = gr.Slider(1, 30, value=8, step=1, label='Inference steps')
guidance = gr.Slider(0, 12, value=7.5, step=0.5, label='Guidance scale')
seed = gr.Number(value=-1, precision=0, label='Seed (-1 = random)')
image_button = gr.Button('Generate image', variant='primary', size='lg')
with gr.Column(scale=3):
image_output = gr.Image(label='Generated image', type='pil', interactive=False)
image_status = gr.Textbox(label='Status', interactive=False)
image_button.click(generate_image, [model, prompt, negative, width, height, steps, guidance, seed], [image_output, image_status])
with gr.Tab('▶ Text to animated video'):
with gr.Row():
with gr.Column(scale=2):
video_model = gr.Dropdown(list(MODELS), value=list(MODELS)[0], label='Image model')
video_prompt = gr.Textbox(lines=4, label='Prompt')
video_negative = gr.Textbox(label='Negative prompt', value='blurry, distorted, low quality')
with gr.Row():
video_width = gr.Dropdown([256, 384, 512], value=256, label='Width')
video_height = gr.Dropdown([256, 384, 512], value=256, label='Height')
video_steps = gr.Slider(1, 30, value=8, step=1, label='Steps')
video_guidance = gr.Slider(0, 12, value=7.5, step=0.5, label='Guidance')
video_seed = gr.Number(value=-1, precision=0, label='Seed (-1 = random)')
with gr.Row():
seconds = gr.Slider(1, 6, value=3, step=1, label='Duration (seconds)')
fps = gr.Slider(4, 16, value=8, step=1, label='FPS')
zoom = gr.Slider(1.0, 1.35, value=1.12, step=0.01, label='Ending zoom')
video_button = gr.Button('Generate animated MP4', variant='primary', size='lg')
with gr.Column(scale=3):
video_still = gr.Image(label='Source image', type='pil')
video_output = gr.Video(label='Animated MP4')
video_status = gr.Textbox(label='Status', interactive=False)
video_button.click(generate_video, [video_model, video_prompt, video_negative, video_width, video_height, video_steps, video_guidance, video_seed, seconds, fps, zoom], [video_still, video_output, video_status])
with gr.Tab('↗ Animate an uploaded image'):
upload = gr.Image(label='Upload image', type='pil')
with gr.Row():
upload_seconds = gr.Slider(1, 6, value=3, step=1, label='Duration (seconds)')
upload_fps = gr.Slider(4, 16, value=8, step=1, label='FPS')
upload_zoom = gr.Slider(1.0, 1.35, value=1.12, step=0.01, label='Ending zoom')
animate_button = gr.Button('Animate image', variant='primary')
upload_video = gr.Video(label='MP4')
upload_status = gr.Textbox(label='Status', interactive=False)
animate_button.click(animate_image, [upload, upload_seconds, upload_fps, upload_zoom], [upload_video, upload_status])
gr.Markdown('**CPU tip:** Start with Tiny Stable Diffusion at 256×256 and 8 steps. Stable Diffusion 1.5 is significantly slower and may exceed the memory available on a free CPU Space.')
if __name__ == '__main__':
demo.queue(default_concurrency_limit=1, max_size=5).launch(server_name='0.0.0.0', server_port=int(os.environ.get('PORT', '7860')))