Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,7 +11,7 @@ import tempfile
|
|
| 11 |
import time
|
| 12 |
import base64
|
| 13 |
import json
|
| 14 |
-
from typing import Dict, List, Tuple
|
| 15 |
from fastapi import FastAPI, HTTPException
|
| 16 |
from pydantic import BaseModel
|
| 17 |
import random
|
|
@@ -37,9 +37,14 @@ class StoryScene(BaseModel):
|
|
| 37 |
visual: str
|
| 38 |
text: str
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
class StorybookRequest(BaseModel):
|
| 41 |
story_title: str
|
| 42 |
scenes: List[StoryScene]
|
|
|
|
| 43 |
model_choice: str = "dreamshaper-8"
|
| 44 |
style: str = "childrens_book"
|
| 45 |
|
|
@@ -57,6 +62,10 @@ model_cache = {}
|
|
| 57 |
current_model_name = None
|
| 58 |
pipe = None
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
def load_model(model_name="dreamshaper-8"):
|
| 61 |
"""Load model into global cache - runs only once per model"""
|
| 62 |
global model_cache, current_model_name, pipe
|
|
@@ -189,8 +198,28 @@ def save_complete_storybook_page(image, story_title, sequence_number, scene_text
|
|
| 189 |
except Exception as e:
|
| 190 |
return f"β Save failed: {str(e)}"
|
| 191 |
|
| 192 |
-
def
|
| 193 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
global pipe, current_model_name
|
| 195 |
|
| 196 |
try:
|
|
@@ -199,10 +228,27 @@ def generate_storybook_page(scene_visual, story_title, sequence_number, scene_te
|
|
| 199 |
print(f"π Switching to model: {model_choice}")
|
| 200 |
pipe = load_model(model_choice)
|
| 201 |
|
| 202 |
-
#
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
print(f"π Generating page {sequence_number} for: {story_title}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
# Generate high-quality image
|
| 208 |
image = pipe(
|
|
@@ -212,7 +258,7 @@ def generate_storybook_page(scene_visual, story_title, sequence_number, scene_te
|
|
| 212 |
guidance_scale=8.5,
|
| 213 |
width=768,
|
| 214 |
height=768,
|
| 215 |
-
generator=
|
| 216 |
).images[0]
|
| 217 |
|
| 218 |
# Save both image and text
|
|
@@ -223,15 +269,23 @@ def generate_storybook_page(scene_visual, story_title, sequence_number, scene_te
|
|
| 223 |
except Exception as e:
|
| 224 |
return None, f"β Generation failed: {str(e)}"
|
| 225 |
|
| 226 |
-
def batch_generate_complete_storybook(story_title, scenes_data, model_choice="dreamshaper-8", style="childrens_book"):
|
| 227 |
"""Generate complete storybook with images and text - MODEL LOADS ONLY ONCE"""
|
|
|
|
|
|
|
| 228 |
results = []
|
| 229 |
status_messages = []
|
| 230 |
|
| 231 |
print(f"π Starting batch generation for: {story_title}")
|
| 232 |
print(f"π Total pages: {len(scenes_data)}")
|
|
|
|
| 233 |
print(f"π¨ Using model: {model_choice}")
|
| 234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
# Load model once at the beginning
|
| 236 |
global pipe
|
| 237 |
pipe = load_model(model_choice)
|
|
@@ -244,7 +298,7 @@ def batch_generate_complete_storybook(story_title, scenes_data, model_choice="dr
|
|
| 244 |
|
| 245 |
print(f"π Generating page {i}/{len(scenes_data)}...")
|
| 246 |
image, status = generate_storybook_page(
|
| 247 |
-
scene_visual, story_title, i, scene_text, model_choice, style
|
| 248 |
)
|
| 249 |
|
| 250 |
if image:
|
|
@@ -260,12 +314,15 @@ def batch_generate_complete_storybook(story_title, scenes_data, model_choice="dr
|
|
| 260 |
# FastAPI endpoint for n8n
|
| 261 |
@app.post("/api/generate-storybook")
|
| 262 |
async def api_generate_storybook(request: StorybookRequest):
|
| 263 |
-
"""API endpoint for n8n automation - OPTIMIZED"""
|
| 264 |
try:
|
| 265 |
print(f"π Received storybook request: {request.story_title}")
|
| 266 |
print(f"π Pages to generate: {len(request.scenes)}")
|
| 267 |
-
print(f"
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
# Convert to scene data format
|
| 271 |
scenes_data = [{"visual": scene.visual, "text": scene.text} for scene in request.scenes]
|
|
@@ -274,6 +331,7 @@ async def api_generate_storybook(request: StorybookRequest):
|
|
| 274 |
results, status = batch_generate_complete_storybook(
|
| 275 |
request.story_title,
|
| 276 |
scenes_data,
|
|
|
|
| 277 |
request.model_choice,
|
| 278 |
request.style
|
| 279 |
)
|
|
@@ -282,6 +340,7 @@ async def api_generate_storybook(request: StorybookRequest):
|
|
| 282 |
"status": "success",
|
| 283 |
"story_title": request.story_title,
|
| 284 |
"total_pages": len(request.scenes),
|
|
|
|
| 285 |
"generated_pages": len(results),
|
| 286 |
"message": status,
|
| 287 |
"folder_path": f"storybook-library/stories/{request.story_title.replace(' ', '_')}/",
|
|
@@ -297,6 +356,8 @@ async def api_generate_storybook(request: StorybookRequest):
|
|
| 297 |
except Exception as e:
|
| 298 |
error_msg = f"Storybook generation failed: {str(e)}"
|
| 299 |
print(f"β {error_msg}")
|
|
|
|
|
|
|
| 300 |
raise HTTPException(status_code=500, detail=error_msg)
|
| 301 |
|
| 302 |
# Health check endpoint
|
|
@@ -308,58 +369,34 @@ async def health_check():
|
|
| 308 |
"timestamp": datetime.now().isoformat(),
|
| 309 |
"models_loaded": list(model_cache.keys()),
|
| 310 |
"current_model": current_model_name,
|
| 311 |
-
"cached_models_count": len(model_cache)
|
|
|
|
| 312 |
}
|
| 313 |
|
| 314 |
-
#
|
| 315 |
-
|
| 316 |
-
# Gradio Interface Functions
|
| 317 |
def generate_single_page(prompt, story_title, scene_text, model_choice, style):
|
| 318 |
"""Generate a single page for Gradio interface"""
|
| 319 |
if not prompt or not story_title:
|
| 320 |
return None, "β Please enter both scene description and story title"
|
| 321 |
|
| 322 |
image, status = generate_storybook_page(
|
| 323 |
-
prompt, story_title, 1, scene_text or "", model_choice, style
|
| 324 |
)
|
| 325 |
return image, status
|
| 326 |
|
| 327 |
-
def generate_full_storybook(story_title, scenes_text, model_choice, style):
|
| 328 |
-
"""Generate full storybook for Gradio interface"""
|
| 329 |
-
if not story_title or not scenes_text:
|
| 330 |
-
return [], "β Please provide story title and scenes"
|
| 331 |
-
|
| 332 |
-
# Parse scenes from text input
|
| 333 |
-
scenes = []
|
| 334 |
-
for i, line in enumerate(scenes_text.split('\n')):
|
| 335 |
-
if line.strip():
|
| 336 |
-
scenes.append({
|
| 337 |
-
"visual": line.strip(),
|
| 338 |
-
"text": f"Page {i+1} text description"
|
| 339 |
-
})
|
| 340 |
-
|
| 341 |
-
results, status = batch_generate_complete_storybook(
|
| 342 |
-
story_title, scenes, model_choice, style
|
| 343 |
-
)
|
| 344 |
-
return results, status
|
| 345 |
-
|
| 346 |
# Create the Gradio interface
|
| 347 |
with gr.Blocks(title="Storybook Generator", theme="soft") as demo:
|
| 348 |
gr.Markdown("# π Storybook Generator")
|
| 349 |
-
gr.Markdown("Create beautiful storybooks with
|
| 350 |
|
| 351 |
with gr.Row():
|
| 352 |
with gr.Column(scale=1):
|
| 353 |
-
gr.Markdown("### π Story Information")
|
| 354 |
-
|
| 355 |
story_title_input = gr.Textbox(
|
| 356 |
label="Story Title",
|
| 357 |
placeholder="Enter your story title...",
|
| 358 |
lines=1
|
| 359 |
)
|
| 360 |
|
| 361 |
-
gr.Markdown("### π― Quality Settings")
|
| 362 |
-
|
| 363 |
model_choice = gr.Dropdown(
|
| 364 |
label="AI Model",
|
| 365 |
choices=list(MODEL_CHOICES.keys()),
|
|
@@ -373,8 +410,6 @@ with gr.Blocks(title="Storybook Generator", theme="soft") as demo:
|
|
| 373 |
)
|
| 374 |
|
| 375 |
with gr.Column(scale=2):
|
| 376 |
-
gr.Markdown("### π¨ Single Page Generation")
|
| 377 |
-
|
| 378 |
prompt_input = gr.Textbox(
|
| 379 |
label="Visual Description",
|
| 380 |
placeholder="Describe the scene for image generation...",
|
|
@@ -391,35 +426,11 @@ with gr.Blocks(title="Storybook Generator", theme="soft") as demo:
|
|
| 391 |
image_output = gr.Image(label="Generated Page", height=400)
|
| 392 |
status_output = gr.Textbox(label="Status", interactive=False)
|
| 393 |
|
| 394 |
-
with gr.Row():
|
| 395 |
-
gr.Markdown("### π Complete Storybook Generation")
|
| 396 |
-
|
| 397 |
-
with gr.Row():
|
| 398 |
-
with gr.Column():
|
| 399 |
-
scenes_input = gr.Textbox(
|
| 400 |
-
label="Visual Descriptions (One per line)",
|
| 401 |
-
placeholder="Enter each page's visual description on separate lines...",
|
| 402 |
-
lines=6
|
| 403 |
-
)
|
| 404 |
-
|
| 405 |
-
batch_btn = gr.Button("π Generate Complete Storybook", variant="primary")
|
| 406 |
-
|
| 407 |
-
with gr.Column():
|
| 408 |
-
batch_status = gr.Textbox(label="Generation Status", interactive=False, lines=6)
|
| 409 |
-
batch_gallery = gr.Gallery(label="Storybook Pages", columns=2, height=600)
|
| 410 |
-
|
| 411 |
-
# Connect buttons to functions
|
| 412 |
generate_btn.click(
|
| 413 |
fn=generate_single_page,
|
| 414 |
inputs=[prompt_input, story_title_input, text_input, model_choice, style_choice],
|
| 415 |
outputs=[image_output, status_output]
|
| 416 |
)
|
| 417 |
-
|
| 418 |
-
batch_btn.click(
|
| 419 |
-
fn=generate_full_storybook,
|
| 420 |
-
inputs=[story_title_input, scenes_input, model_choice, style_choice],
|
| 421 |
-
outputs=[batch_gallery, batch_status]
|
| 422 |
-
)
|
| 423 |
|
| 424 |
# Mount Gradio app to FastAPI
|
| 425 |
app = gr.mount_gradio_app(app, demo, path="/")
|
|
|
|
| 11 |
import time
|
| 12 |
import base64
|
| 13 |
import json
|
| 14 |
+
from typing import Dict, List, Tuple, Optional
|
| 15 |
from fastapi import FastAPI, HTTPException
|
| 16 |
from pydantic import BaseModel
|
| 17 |
import random
|
|
|
|
| 37 |
visual: str
|
| 38 |
text: str
|
| 39 |
|
| 40 |
+
class CharacterDescription(BaseModel):
|
| 41 |
+
name: str
|
| 42 |
+
description: str
|
| 43 |
+
|
| 44 |
class StorybookRequest(BaseModel):
|
| 45 |
story_title: str
|
| 46 |
scenes: List[StoryScene]
|
| 47 |
+
characters: List[CharacterDescription] = []
|
| 48 |
model_choice: str = "dreamshaper-8"
|
| 49 |
style: str = "childrens_book"
|
| 50 |
|
|
|
|
| 62 |
current_model_name = None
|
| 63 |
pipe = None
|
| 64 |
|
| 65 |
+
# Character consistency tracking
|
| 66 |
+
character_descriptions = {}
|
| 67 |
+
character_seeds = {} # Store seeds for consistent character generation
|
| 68 |
+
|
| 69 |
def load_model(model_name="dreamshaper-8"):
|
| 70 |
"""Load model into global cache - runs only once per model"""
|
| 71 |
global model_cache, current_model_name, pipe
|
|
|
|
| 198 |
except Exception as e:
|
| 199 |
return f"β Save failed: {str(e)}"
|
| 200 |
|
| 201 |
+
def enhance_with_character_context(scene_visual, story_title, characters):
|
| 202 |
+
"""Add character descriptions to maintain consistency"""
|
| 203 |
+
if characters:
|
| 204 |
+
character_context = " ".join([f"{char.name}: {char.description}" for char in characters])
|
| 205 |
+
return f"Character descriptions: {character_context}. {scene_visual}"
|
| 206 |
+
return scene_visual
|
| 207 |
+
|
| 208 |
+
def get_character_seed(story_title, character_name):
|
| 209 |
+
"""Get consistent seed for character generation"""
|
| 210 |
+
if story_title not in character_seeds:
|
| 211 |
+
character_seeds[story_title] = {}
|
| 212 |
+
|
| 213 |
+
if character_name not in character_seeds[story_title]:
|
| 214 |
+
# Generate a stable seed based on character name and story title
|
| 215 |
+
seed_value = hash(f"{story_title}_{character_name}") % 1000000
|
| 216 |
+
character_seeds[story_title][character_name] = seed_value
|
| 217 |
+
print(f"π± Seed for {character_name}: {seed_value}")
|
| 218 |
+
|
| 219 |
+
return character_seeds[story_title][character_name]
|
| 220 |
+
|
| 221 |
+
def generate_storybook_page(scene_visual, story_title, sequence_number, scene_text, characters, model_choice="dreamshaper-8", style="childrens_book"):
|
| 222 |
+
"""Generate a storybook page with character consistency"""
|
| 223 |
global pipe, current_model_name
|
| 224 |
|
| 225 |
try:
|
|
|
|
| 228 |
print(f"π Switching to model: {model_choice}")
|
| 229 |
pipe = load_model(model_choice)
|
| 230 |
|
| 231 |
+
# ENHANCE PROMPT WITH CHARACTER CONTEXT
|
| 232 |
+
enhanced_visual = enhance_with_character_context(scene_visual, story_title, characters)
|
| 233 |
+
|
| 234 |
+
# Add scene continuity context
|
| 235 |
+
if sequence_number > 1:
|
| 236 |
+
enhanced_visual = f"Scene {sequence_number}, maintain character consistency from previous scenes. {enhanced_visual}"
|
| 237 |
+
|
| 238 |
+
enhanced_prompt, negative_prompt = enhance_prompt(enhanced_visual, style)
|
| 239 |
|
| 240 |
print(f"π Generating page {sequence_number} for: {story_title}")
|
| 241 |
+
if characters:
|
| 242 |
+
print(f"π€ Characters: {[char.name for char in characters]}")
|
| 243 |
+
|
| 244 |
+
# Use consistent seed for character generation
|
| 245 |
+
generator = torch.Generator(device="cpu")
|
| 246 |
+
if characters:
|
| 247 |
+
# Use seed from main character for consistency
|
| 248 |
+
main_char_seed = get_character_seed(story_title, characters[0].name)
|
| 249 |
+
generator.manual_seed(main_char_seed)
|
| 250 |
+
else:
|
| 251 |
+
generator.manual_seed(int(time.time()))
|
| 252 |
|
| 253 |
# Generate high-quality image
|
| 254 |
image = pipe(
|
|
|
|
| 258 |
guidance_scale=8.5,
|
| 259 |
width=768,
|
| 260 |
height=768,
|
| 261 |
+
generator=generator
|
| 262 |
).images[0]
|
| 263 |
|
| 264 |
# Save both image and text
|
|
|
|
| 269 |
except Exception as e:
|
| 270 |
return None, f"β Generation failed: {str(e)}"
|
| 271 |
|
| 272 |
+
def batch_generate_complete_storybook(story_title, scenes_data, characters, model_choice="dreamshaper-8", style="childrens_book"):
|
| 273 |
"""Generate complete storybook with images and text - MODEL LOADS ONLY ONCE"""
|
| 274 |
+
global character_descriptions
|
| 275 |
+
|
| 276 |
results = []
|
| 277 |
status_messages = []
|
| 278 |
|
| 279 |
print(f"π Starting batch generation for: {story_title}")
|
| 280 |
print(f"π Total pages: {len(scenes_data)}")
|
| 281 |
+
print(f"π€ Characters: {len(characters)}")
|
| 282 |
print(f"π¨ Using model: {model_choice}")
|
| 283 |
|
| 284 |
+
# Store character descriptions for this story
|
| 285 |
+
if characters:
|
| 286 |
+
character_descriptions[story_title] = characters
|
| 287 |
+
print(f"β
Character context stored for {story_title}")
|
| 288 |
+
|
| 289 |
# Load model once at the beginning
|
| 290 |
global pipe
|
| 291 |
pipe = load_model(model_choice)
|
|
|
|
| 298 |
|
| 299 |
print(f"π Generating page {i}/{len(scenes_data)}...")
|
| 300 |
image, status = generate_storybook_page(
|
| 301 |
+
scene_visual, story_title, i, scene_text, characters, model_choice, style
|
| 302 |
)
|
| 303 |
|
| 304 |
if image:
|
|
|
|
| 314 |
# FastAPI endpoint for n8n
|
| 315 |
@app.post("/api/generate-storybook")
|
| 316 |
async def api_generate_storybook(request: StorybookRequest):
|
| 317 |
+
"""API endpoint for n8n automation - OPTIMIZED with character consistency"""
|
| 318 |
try:
|
| 319 |
print(f"π Received storybook request: {request.story_title}")
|
| 320 |
print(f"π Pages to generate: {len(request.scenes)}")
|
| 321 |
+
print(f"π€ Characters received: {len(request.characters)}")
|
| 322 |
+
|
| 323 |
+
if request.characters:
|
| 324 |
+
for char in request.characters:
|
| 325 |
+
print(f" - {char.name}: {char.description[:50]}...")
|
| 326 |
|
| 327 |
# Convert to scene data format
|
| 328 |
scenes_data = [{"visual": scene.visual, "text": scene.text} for scene in request.scenes]
|
|
|
|
| 331 |
results, status = batch_generate_complete_storybook(
|
| 332 |
request.story_title,
|
| 333 |
scenes_data,
|
| 334 |
+
request.characters,
|
| 335 |
request.model_choice,
|
| 336 |
request.style
|
| 337 |
)
|
|
|
|
| 340 |
"status": "success",
|
| 341 |
"story_title": request.story_title,
|
| 342 |
"total_pages": len(request.scenes),
|
| 343 |
+
"characters_used": len(request.characters),
|
| 344 |
"generated_pages": len(results),
|
| 345 |
"message": status,
|
| 346 |
"folder_path": f"storybook-library/stories/{request.story_title.replace(' ', '_')}/",
|
|
|
|
| 356 |
except Exception as e:
|
| 357 |
error_msg = f"Storybook generation failed: {str(e)}"
|
| 358 |
print(f"β {error_msg}")
|
| 359 |
+
import traceback
|
| 360 |
+
traceback.print_exc()
|
| 361 |
raise HTTPException(status_code=500, detail=error_msg)
|
| 362 |
|
| 363 |
# Health check endpoint
|
|
|
|
| 369 |
"timestamp": datetime.now().isoformat(),
|
| 370 |
"models_loaded": list(model_cache.keys()),
|
| 371 |
"current_model": current_model_name,
|
| 372 |
+
"cached_models_count": len(model_cache),
|
| 373 |
+
"stories_tracked": len(character_descriptions)
|
| 374 |
}
|
| 375 |
|
| 376 |
+
# Gradio Interface Functions (simplified)
|
|
|
|
|
|
|
| 377 |
def generate_single_page(prompt, story_title, scene_text, model_choice, style):
|
| 378 |
"""Generate a single page for Gradio interface"""
|
| 379 |
if not prompt or not story_title:
|
| 380 |
return None, "β Please enter both scene description and story title"
|
| 381 |
|
| 382 |
image, status = generate_storybook_page(
|
| 383 |
+
prompt, story_title, 1, scene_text or "", [], model_choice, style
|
| 384 |
)
|
| 385 |
return image, status
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
# Create the Gradio interface
|
| 388 |
with gr.Blocks(title="Storybook Generator", theme="soft") as demo:
|
| 389 |
gr.Markdown("# π Storybook Generator")
|
| 390 |
+
gr.Markdown("Create beautiful storybooks with consistent characters")
|
| 391 |
|
| 392 |
with gr.Row():
|
| 393 |
with gr.Column(scale=1):
|
|
|
|
|
|
|
| 394 |
story_title_input = gr.Textbox(
|
| 395 |
label="Story Title",
|
| 396 |
placeholder="Enter your story title...",
|
| 397 |
lines=1
|
| 398 |
)
|
| 399 |
|
|
|
|
|
|
|
| 400 |
model_choice = gr.Dropdown(
|
| 401 |
label="AI Model",
|
| 402 |
choices=list(MODEL_CHOICES.keys()),
|
|
|
|
| 410 |
)
|
| 411 |
|
| 412 |
with gr.Column(scale=2):
|
|
|
|
|
|
|
| 413 |
prompt_input = gr.Textbox(
|
| 414 |
label="Visual Description",
|
| 415 |
placeholder="Describe the scene for image generation...",
|
|
|
|
| 426 |
image_output = gr.Image(label="Generated Page", height=400)
|
| 427 |
status_output = gr.Textbox(label="Status", interactive=False)
|
| 428 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
generate_btn.click(
|
| 430 |
fn=generate_single_page,
|
| 431 |
inputs=[prompt_input, story_title_input, text_input, model_choice, style_choice],
|
| 432 |
outputs=[image_output, status_output]
|
| 433 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
|
| 435 |
# Mount Gradio app to FastAPI
|
| 436 |
app = gr.mount_gradio_app(app, demo, path="/")
|