"""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('
Image creation and animated video · Hugging Face CPU Spaces · No GPU required