Spaces:
Build error
Build error
File size: 19,433 Bytes
adfd61d db0fb8b e881598 adfd61d dd35031 adfd61d de40a85 c68efab bf3691d 83dc960 4c07f93 f2a8121 83dc960 d661abf bf3691d 1e600eb 79c85c7 764f61d e881598 1bd1797 8d00cc9 e881598 b35ca83 1e600eb b35ca83 1e600eb b35ca83 4c07f93 e881598 4c07f93 83dc960 4c07f93 e881598 4c07f93 dbad725 e881598 dbad725 4c07f93 dbad725 e881598 4c07f93 02f8f98 17d8ccf c0034ac 83dc960 02f8f98 83dc960 79c85c7 bf3691d 8fc430a 83dc960 b272d86 f9587af 6bf397f b272d86 e881598 bf3691d b272d86 6bf397f 8fc430a e881598 6bf397f bf3691d 6bf397f 79c85c7 c0034ac bf3691d c0034ac 79c85c7 c0034ac bf3691d c0034ac bf3691d 79c85c7 bf3691d c0034ac 6bf397f bf3691d 6bf397f 79c85c7 e881598 79c85c7 bf3691d 79c85c7 bf3691d 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 bf3691d 79c85c7 1e600eb 79c85c7 bf3691d 79c85c7 bf3691d 17d8ccf e881598 bf3691d 5237974 8e7984f e881598 17d8ccf 8e7984f 17d8ccf 5237974 d88eca0 8e7984f d88eca0 8e7984f bf3691d 05fe702 bf3691d 8e7984f d88eca0 bf3691d d88eca0 83dc960 764f61d e881598 79c85c7 8e7984f e881598 8e7984f 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 79c85c7 1e600eb 8e7984f 1e600eb 8e7984f 1e600eb 8e7984f 79c85c7 671d16d 8e7984f 1e600eb bf3691d 1e600eb 17d8ccf 1e600eb 17d8ccf 1e600eb 17d8ccf 8e7984f 1e600eb 8e7984f 1e600eb 8e7984f 1e600eb 8e7984f 1e600eb 8e7984f 1e600eb 8e7984f 1e600eb bf3691d 79c85c7 8e7984f 1e600eb 17d8ccf e881598 17d8ccf e881598 17d8ccf bf3691d 8e7984f e881598 79c85c7 c68efab 17d8ccf 8e7984f bf3691d 671d16d 8e7984f e881598 8e7984f bf3691d 83dc960 bf3691d 83dc960 8e7984f 1e600eb 79c85c7 1d24354 8e7984f 4c07f93 8e7984f 4c07f93 8e7984f bf3691d 02f8f98 83dc960 02f8f98 4c07f93 8e7984f bf3691d 8e7984f e881598 8e7984f 79c85c7 8e7984f d661abf bf3691d 79c85c7 bf3691d 1e600eb bf3691d 671d16d 79c85c7 bf3691d 79c85c7 671d16d 79c85c7 e881598 79c85c7 e881598 bf3691d 671d16d 79c85c7 bf3691d 7134053 671d16d bf3691d 79c85c7 bf3691d 5c6748a bf3691d 5c6748a 8d00cc9 8e7984f 79c85c7 | 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 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 | import gradio as gr
import torch
from diffusers import StableDiffusionPipeline, EulerAncestralDiscreteScheduler
from PIL import Image
import io
import requests
import os
from datetime import datetime
import time
import json
from typing import List, Optional
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
import threading
import uuid
import random
from enum import Enum
import numpy as np
# Try to import optional dependencies
try:
from rembg import remove
REMBG_AVAILABLE = True
except ImportError:
REMBG_AVAILABLE = False
print("β οΈ rembg not available, character transparency disabled")
# External OCI API URL
OCI_API_BASE_URL = "https://yukee1992-oci-story-book.hf.space"
# Create local directories
PERSISTENT_IMAGE_DIR = "generated_test_images"
CHARACTERS_DIR = "characters"
os.makedirs(PERSISTENT_IMAGE_DIR, exist_ok=True)
os.makedirs(CHARACTERS_DIR, exist_ok=True)
print(f"π Created local directories")
# Initialize FastAPI app
app = FastAPI(title="Storybook Generator API")
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class JobStatus(str, Enum):
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
class StoryScene(BaseModel):
visual: str
text: str
characters_present: List[str] = []
class CharacterDescription(BaseModel):
name: str
description: str
visual_prompt: str = ""
key_features: List[str] = []
class StorybookRequest(BaseModel):
story_title: str
scenes: List[StoryScene]
characters: List[CharacterDescription] = []
model_choice: str = "sd-1.5"
style: str = "childrens_book"
callback_url: Optional[str] = None
consistency_seed: Optional[int] = None
class JobStatusResponse(BaseModel):
job_id: str
status: JobStatus
progress: int
message: str
result: Optional[dict] = None
created_at: float
updated_at: float
# Model configuration - Using smaller model for better compatibility
MODEL_CONFIG = {
"sd-1.5": {
"model_id": "runwayml/stable-diffusion-v1-5",
"revision": "fp16",
"torch_dtype": torch.float16
}
}
job_storage = {}
model_cache = {}
current_pipe = None
model_lock = threading.Lock()
def load_model(model_name="sd-1.5"):
"""Load model with version compatibility"""
global model_cache, current_pipe
with model_lock:
if model_name in model_cache:
current_pipe = model_cache[model_name]
return current_pipe
print(f"π Loading model: {model_name}")
try:
config = MODEL_CONFIG[model_name]
# Use simpler loading
pipe = StableDiffusionPipeline.from_pretrained(
config["model_id"],
torch_dtype=config["torch_dtype"],
safety_checker=None,
requires_safety_checker=False
)
# Configure scheduler
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
# Move to appropriate device
if torch.cuda.is_available():
pipe = pipe.to("cuda")
print("β
Using CUDA")
else:
pipe = pipe.to("cpu")
print("β
Using CPU")
# Enable memory efficient attention
pipe.enable_attention_slicing()
model_cache[model_name] = pipe
current_pipe = pipe
print(f"β
Model loaded successfully: {model_name}")
return pipe
except Exception as e:
print(f"β Model loading failed: {e}")
# Try fallback model
try:
print("π Trying fallback model...")
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float32
)
pipe = pipe.to("cuda" if torch.cuda.is_available() else "cpu")
pipe.enable_attention_slicing()
model_cache[model_name] = pipe
current_pipe = pipe
print("β
Fallback model loaded successfully")
return pipe
except Exception as fallback_error:
print(f"β Fallback model also failed: {fallback_error}")
raise e
def generate_simple_image(prompt, negative_prompt="", seed=None, width=512, height=512):
"""Simple image generation with error handling"""
try:
pipe = load_model("sd-1.5")
if pipe is None:
raise Exception("Model not available")
generator = None
if seed:
generator = torch.Generator(device=pipe.device).manual_seed(seed)
# Generate image
with torch.autocast(pipe.device.type if pipe.device.type != 'mps' else 'cpu'):
result = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
num_inference_steps=20,
guidance_scale=7.5,
width=width,
height=height,
generator=generator
)
return result.images[0]
except Exception as e:
print(f"β Image generation failed: {e}")
# Create a simple error image
error_image = Image.new('RGB', (width, height), color='red')
return error_image
def generate_character_image(character_desc, seed=None):
"""Generate character image"""
try:
character_prompt = f"{character_desc.visual_prompt or character_desc.description}, character design, clean lines, isolated on plain background, cartoon style, children's book illustration"
negative_prompt = "blurry, low quality, complex background, multiple characters, dark, scary"
image = generate_simple_image(
character_prompt,
negative_prompt,
seed,
width=512,
height=512
)
# If rembg is available, remove background
if REMBG_AVAILABLE:
try:
image = remove(image)
except Exception as bg_error:
print(f"β οΈ Background removal failed: {bg_error}")
# Convert to RGBA anyway
image = image.convert('RGBA')
else:
image = image.convert('RGBA')
return image
except Exception as e:
print(f"β Character generation failed: {e}")
error_image = Image.new('RGBA', (512, 512), (255, 0, 0, 128))
return error_image
def save_to_oci_bucket(file_data, filename, story_title, file_type="image"):
"""Save files to OCI bucket with fallback"""
try:
api_url = f"{OCI_API_BASE_URL}/api/upload"
full_subfolder = f'stories/{story_title}'
mime_type = "image/png" if file_type == "image" else "text/plain"
files = {'file': (filename, file_data, mime_type)}
data = {
'project_id': 'storybook-library',
'subfolder': full_subfolder
}
response = requests.post(api_url, files=files, data=data, timeout=30)
if response.status_code == 200:
result = response.json()
if result['status'] == 'success':
return result.get('file_url', 'Unknown URL')
else:
print(f"β οΈ OCI API Error: {result.get('message', 'Unknown error')}")
return f"local://{filename}"
else:
print(f"β οΈ HTTP Error: {response.status_code}")
return f"local://{filename}"
except Exception as e:
print(f"β οΈ OCI upload failed, using local fallback: {str(e)}")
return f"local://{filename}"
def create_job(story_request: StorybookRequest) -> str:
job_id = str(uuid.uuid4())
job_storage[job_id] = {
"status": JobStatus.PENDING,
"progress": 0,
"message": "Job created and queued",
"request": story_request.dict(),
"result": None,
"created_at": time.time(),
"updated_at": time.time(),
}
print(f"π Created job {job_id} for story: {story_request.story_title}")
return job_id
def update_job_status(job_id: str, status: JobStatus, progress: int, message: str, result=None):
if job_id not in job_storage:
return False
job_storage[job_id].update({
"status": status,
"progress": progress,
"message": message,
"updated_at": time.time()
})
if result:
job_storage[job_id]["result"] = result
return True
def generate_storybook_background(job_id: str):
"""Background task for storybook generation"""
try:
job_data = job_storage[job_id]
story_request_data = job_data["request"]
story_request = StorybookRequest(**story_request_data)
print(f"π¬ Starting storybook generation for job {job_id}")
update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
# Generate characters first
character_urls = {}
if story_request.characters:
update_job_status(job_id, JobStatus.PROCESSING, 10, "Generating characters...")
for i, character in enumerate(story_request.characters):
progress = 10 + int((i / len(story_request.characters)) * 30)
update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating character: {character.name}")
try:
print(f"π€ Generating character: {character.name}")
character_image = generate_character_image(
character,
story_request.consistency_seed
)
# Save character locally
char_filename = f"character_{character.name}_{job_id}.png"
char_local_path = os.path.join(CHARACTERS_DIR, char_filename)
character_image.save(char_local_path, 'PNG')
# Upload to OCI
img_bytes = io.BytesIO()
character_image.save(img_bytes, format='PNG')
character_url = save_to_oci_bucket(
img_bytes.getvalue(),
f"character_{character.name}.png",
story_request.story_title,
"image"
)
character_urls[character.name] = {
"url": character_url,
"local_path": char_local_path
}
print(f"β
Character {character.name} completed")
except Exception as e:
error_msg = f"Failed to generate character {character.name}: {str(e)}"
print(f"β {error_msg}")
character_urls[character.name] = {"url": f"error_{character.name}", "local_path": ""}
# Generate scenes
update_job_status(job_id, JobStatus.PROCESSING, 40, "Generating scenes...")
generated_pages = []
for i, scene in enumerate(story_request.scenes):
progress = 40 + int((i / len(story_request.scenes)) * 55)
update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating scene {i+1}/{len(story_request.scenes)}...")
try:
print(f"πΌοΈ Generating scene {i+1}")
# Enhanced scene prompt with character context
character_context = ""
if scene.characters_present:
character_context = f" featuring {', '.join(scene.characters_present)}"
scene_prompt = f"children's book illustration, {scene.visual}{character_context}, colorful, clean, professional artwork"
negative_prompt = "blurry, low quality, bad anatomy, dark, scary"
scene_image = generate_simple_image(
scene_prompt,
negative_prompt,
story_request.consistency_seed
)
# Save scene locally
scene_filename = f"scene_{i+1:03d}_{job_id}.png"
scene_local_path = os.path.join(PERSISTENT_IMAGE_DIR, scene_filename)
scene_image.save(scene_local_path, 'PNG')
# Upload to OCI
img_bytes = io.BytesIO()
scene_image.save(img_bytes, format='PNG')
scene_url = save_to_oci_bucket(
img_bytes.getvalue(),
f"scene_{i+1:03d}.png",
story_request.story_title,
"image"
)
page_data = {
"page_number": i + 1,
"image_url": scene_url,
"local_path": scene_local_path,
"text": scene.text,
"characters_present": scene.characters_present
}
generated_pages.append(page_data)
print(f"β
Scene {i+1} completed")
except Exception as e:
error_msg = f"Failed to generate scene {i+1}: {str(e)}"
print(f"β {error_msg}")
page_data = {
"page_number": i + 1,
"image_url": f"error_scene_{i+1}",
"local_path": "",
"text": scene.text,
"characters_present": scene.characters_present,
"error": error_msg
}
generated_pages.append(page_data)
# Final result
result = {
"story_title": story_request.story_title,
"total_pages": len(generated_pages),
"total_characters": len(character_urls),
"characters": character_urls,
"pages": generated_pages,
"job_id": job_id,
"rembg_available": REMBG_AVAILABLE
}
update_job_status(
job_id,
JobStatus.COMPLETED,
100,
f"π Storybook completed! {len(generated_pages)} scenes and {len(character_urls)} characters generated.",
result
)
print(f"π Storybook finished for job {job_id}")
except Exception as e:
error_msg = f"Story generation failed: {str(e)}"
print(f"β {error_msg}")
update_job_status(job_id, JobStatus.FAILED, 0, error_msg)
# API Routes
@app.post("/api/generate-storybook")
async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
"""Storybook generation endpoint"""
try:
print(f"π₯ Received storybook request: {request.get('story_title', 'Unknown')}")
# Set default seed if not provided
if 'consistency_seed' not in request or not request['consistency_seed']:
request['consistency_seed'] = random.randint(1000, 9999)
story_request = StorybookRequest(**request)
if not story_request.story_title or not story_request.scenes:
raise HTTPException(status_code=400, detail="story_title and scenes are required")
job_id = create_job(story_request)
background_tasks.add_task(generate_storybook_background, job_id)
return {
"status": "success",
"message": "Storybook generation started",
"job_id": job_id,
"story_title": story_request.story_title,
"total_scenes": len(story_request.scenes),
"total_characters": len(story_request.characters),
"consistency_seed": story_request.consistency_seed,
"rembg_available": REMBG_AVAILABLE
}
except Exception as e:
error_msg = f"API Error: {str(e)}"
print(f"β {error_msg}")
raise HTTPException(status_code=500, detail=error_msg)
@app.get("/api/job-status/{job_id}")
async def get_job_status(job_id: str):
job_data = job_storage.get(job_id)
if not job_data:
raise HTTPException(status_code=404, detail="Job not found")
return JobStatusResponse(
job_id=job_id,
status=job_data["status"],
progress=job_data["progress"],
message=job_data["message"],
result=job_data["result"],
created_at=job_data["created_at"],
updated_at=job_data["updated_at"]
)
@app.get("/api/health")
async def health_check():
return {
"status": "healthy",
"service": "storybook-generator",
"timestamp": datetime.now().isoformat(),
"active_jobs": len(job_storage),
"model_loaded": "sd-1.5" in model_cache,
"rembg_available": REMBG_AVAILABLE
}
@app.get("/")
async def root():
return {"message": "Storybook Generator API", "status": "running"}
# Simple Gradio Interface
def create_test_interface():
with gr.Blocks(title="Storybook Generator Test") as demo:
gr.Markdown("# π¨ Storybook Generator Test")
with gr.Row():
with gr.Column():
test_prompt = gr.Textbox(
label="Test Prompt",
value="a cute cartoon cat reading a book under a tree",
lines=2
)
test_seed = gr.Number(label="Seed", value=42)
generate_btn = gr.Button("Generate Test Image", variant="primary")
with gr.Column():
output_image = gr.Image(label="Generated Image", height=512)
status_text = gr.Textbox(label="Status", interactive=False)
def test_generate(prompt, seed):
try:
status_text = "π Generating image..."
image = generate_simple_image(prompt, seed=seed)
status_text = "β
Image generated successfully!"
return image, status_text
except Exception as e:
error_msg = f"β Error: {str(e)}"
print(error_msg)
return None, error_msg
generate_btn.click(
test_generate,
inputs=[test_prompt, test_seed],
outputs=[output_image, status_text]
)
return demo
# Initialize the app
print("π Initializing Storybook Generator...")
print(f"π¦ rembg available: {REMBG_AVAILABLE}")
try:
# Test model loading
load_model("sd-1.5")
print("β
Model loaded successfully!")
except Exception as e:
print(f"β Model loading failed: {e}")
demo = create_test_interface()
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000) |