Update app.py
Browse files
app.py
CHANGED
|
@@ -98,7 +98,7 @@ class JobStatus(str, Enum):
|
|
| 98 |
FAILED = "failed"
|
| 99 |
|
| 100 |
# =============================================
|
| 101 |
-
#
|
| 102 |
# =============================================
|
| 103 |
|
| 104 |
# Available image styles with their prompts
|
|
@@ -185,7 +185,7 @@ STYLE_CONFIGS = {
|
|
| 185 |
}
|
| 186 |
}
|
| 187 |
|
| 188 |
-
# Size presets
|
| 189 |
SIZE_PRESETS = {
|
| 190 |
"square": {"width": 768, "height": 768, "description": "1:1 Square"},
|
| 191 |
"portrait": {"width": 768, "height": 1024, "description": "3:4 Portrait"},
|
|
@@ -194,8 +194,8 @@ SIZE_PRESETS = {
|
|
| 194 |
"ultrawide": {"width": 1920, "height": 768, "description": "2.5:1 Ultrawide"},
|
| 195 |
"instagram": {"width": 1080, "height": 1080, "description": "1:1 Instagram"},
|
| 196 |
"story": {"width": 1080, "height": 1920, "description": "9:16 Story"},
|
| 197 |
-
"facebook": {"width": 1200, "height":
|
| 198 |
-
"twitter": {"width": 1600, "height":
|
| 199 |
"custom": {"width": 768, "height": 1024, "description": "Custom Size"}
|
| 200 |
}
|
| 201 |
|
|
@@ -214,9 +214,9 @@ class StorybookRequest(BaseModel):
|
|
| 214 |
characters: List[CharacterDescription] = []
|
| 215 |
model_choice: str = "dreamshaper-8"
|
| 216 |
style: str = "childrens_book"
|
| 217 |
-
image_size: str = "portrait"
|
| 218 |
-
custom_width: Optional[int] = None
|
| 219 |
-
custom_height: Optional[int] = None
|
| 220 |
callback_url: Optional[str] = None
|
| 221 |
consistency_seed: Optional[int] = None
|
| 222 |
project_id: Optional[str] = None
|
|
@@ -230,7 +230,7 @@ class InfographicRequest(BaseModel):
|
|
| 230 |
title: str
|
| 231 |
items: List[InfographicItem]
|
| 232 |
style: str = "food_photography"
|
| 233 |
-
image_size: str = "story"
|
| 234 |
custom_width: Optional[int] = None
|
| 235 |
custom_height: Optional[int] = None
|
| 236 |
project_id: Optional[str] = None
|
|
@@ -275,41 +275,51 @@ current_pipe = None
|
|
| 275 |
model_lock = threading.Lock()
|
| 276 |
|
| 277 |
# =============================================
|
| 278 |
-
# HELPER FUNCTIONS
|
| 279 |
# =============================================
|
| 280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
def get_image_dimensions(size_preset, custom_width=None, custom_height=None):
|
| 282 |
-
"""Get image dimensions based on preset or custom values"""
|
| 283 |
if size_preset == "custom" and custom_width and custom_height:
|
| 284 |
-
|
| 285 |
elif size_preset in SIZE_PRESETS:
|
| 286 |
preset = SIZE_PRESETS[size_preset]
|
| 287 |
-
|
| 288 |
else:
|
| 289 |
# Default to portrait
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
def get_style_config(style_name):
|
| 293 |
"""Get style configuration or return default"""
|
| 294 |
if style_name in STYLE_CONFIGS:
|
| 295 |
return STYLE_CONFIGS[style_name]
|
| 296 |
-
# Default to children's book style
|
| 297 |
return STYLE_CONFIGS["childrens_book"]
|
| 298 |
|
| 299 |
def enhance_prompt_with_style(scene_visual, style="childrens_book", custom_style_prompt=None):
|
| 300 |
"""Enhanced prompt with style configuration"""
|
| 301 |
-
|
| 302 |
-
# Get style configuration
|
| 303 |
if custom_style_prompt:
|
| 304 |
style_prefix = custom_style_prompt
|
| 305 |
else:
|
| 306 |
style_config = get_style_config(style)
|
| 307 |
style_prefix = style_config["prompt_prefix"]
|
| 308 |
|
| 309 |
-
# Combine with user prompt
|
| 310 |
enhanced_prompt = f"{style_prefix}, {scene_visual}"
|
| 311 |
-
|
| 312 |
-
# Get negative prompt from style config
|
| 313 |
negative_prompt = style_config.get("negative", "blurry, low quality, bad anatomy, deformed, ugly")
|
| 314 |
|
| 315 |
return enhanced_prompt, negative_prompt
|
|
@@ -318,6 +328,9 @@ def generate_image_with_settings(prompt, model_choice, style, scene_number, cons
|
|
| 318 |
width=768, height=1024, custom_style_prompt=None):
|
| 319 |
"""Generate image with customizable style and dimensions"""
|
| 320 |
|
|
|
|
|
|
|
|
|
|
| 321 |
# Get enhanced prompt with style
|
| 322 |
enhanced_prompt, negative_prompt = enhance_prompt_with_style(prompt, style, custom_style_prompt)
|
| 323 |
|
|
@@ -330,6 +343,12 @@ def generate_image_with_settings(prompt, model_choice, style, scene_number, cons
|
|
| 330 |
try:
|
| 331 |
pipe = load_model(model_choice)
|
| 332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
image = pipe(
|
| 334 |
prompt=enhanced_prompt,
|
| 335 |
negative_prompt=negative_prompt,
|
|
@@ -337,18 +356,20 @@ def generate_image_with_settings(prompt, model_choice, style, scene_number, cons
|
|
| 337 |
guidance_scale=7.5,
|
| 338 |
width=width,
|
| 339 |
height=height,
|
| 340 |
-
generator=
|
| 341 |
).images[0]
|
| 342 |
|
| 343 |
-
print(f"✅ Generated image - Style: {style}, Size: {width}x{height}")
|
| 344 |
return image
|
| 345 |
|
| 346 |
except Exception as e:
|
| 347 |
print(f"❌ Generation failed: {str(e)}")
|
|
|
|
|
|
|
| 348 |
raise
|
| 349 |
|
| 350 |
# =============================================
|
| 351 |
-
#
|
| 352 |
# =============================================
|
| 353 |
|
| 354 |
def ensure_dataset_exists():
|
|
@@ -406,6 +427,10 @@ def upload_image_to_hf_dataset(image, project_id, page_number, prompt, style="")
|
|
| 406 |
print(f"❌ Failed to upload: {e}")
|
| 407 |
return None
|
| 408 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
def get_memory_usage():
|
| 410 |
"""Get current memory usage statistics"""
|
| 411 |
process = psutil.Process()
|
|
@@ -481,6 +506,10 @@ print("🚀 Initializing Enhanced Generator API...")
|
|
| 481 |
load_model("dreamshaper-8")
|
| 482 |
print("✅ Model loaded and ready!")
|
| 483 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
def save_image_to_local(image, prompt, style="test"):
|
| 485 |
"""Save image to local storage"""
|
| 486 |
try:
|
|
@@ -548,6 +577,10 @@ def refresh_local_images():
|
|
| 548 |
except Exception as e:
|
| 549 |
return []
|
| 550 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
def create_job(request) -> str:
|
| 552 |
job_id = str(uuid.uuid4())
|
| 553 |
job_storage[job_id] = {
|
|
@@ -578,7 +611,7 @@ def update_job_status(job_id: str, status: JobStatus, progress: int, message: st
|
|
| 578 |
return True
|
| 579 |
|
| 580 |
# =============================================
|
| 581 |
-
#
|
| 582 |
# =============================================
|
| 583 |
|
| 584 |
def generate_storybook_background(job_id: str):
|
|
@@ -589,14 +622,13 @@ def generate_storybook_background(job_id: str):
|
|
| 589 |
|
| 590 |
project_id = story_request.project_id or story_request.story_title.replace(' ', '_').lower()
|
| 591 |
|
| 592 |
-
# Get dimensions based on size preset
|
| 593 |
width, height = get_image_dimensions(
|
| 594 |
story_request.image_size,
|
| 595 |
story_request.custom_width,
|
| 596 |
story_request.custom_height
|
| 597 |
)
|
| 598 |
|
| 599 |
-
print(f"🎬 Starting storybook
|
| 600 |
|
| 601 |
update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
|
| 602 |
|
|
@@ -609,7 +641,6 @@ def generate_storybook_background(job_id: str):
|
|
| 609 |
update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating page {i+1}/{total_scenes}")
|
| 610 |
|
| 611 |
try:
|
| 612 |
-
# Generate image with style and size
|
| 613 |
image = generate_image_with_settings(
|
| 614 |
scene.visual,
|
| 615 |
story_request.model_choice,
|
|
@@ -659,24 +690,204 @@ def generate_storybook_background(job_id: str):
|
|
| 659 |
update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
|
| 660 |
|
| 661 |
# =============================================
|
| 662 |
-
#
|
| 663 |
# =============================================
|
| 664 |
|
| 665 |
class InfographicLayoutGenerator:
|
| 666 |
-
# Keep your existing InfographicLayoutGenerator class here
|
| 667 |
-
# (I'm keeping it minimal for brevity - include your full class)
|
| 668 |
def __init__(self):
|
| 669 |
self.title_color = (60, 60, 60)
|
| 670 |
self.text_color = (40, 40, 40)
|
| 671 |
self.accent_color = (200, 160, 120)
|
| 672 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None, width=900, height=1600):
|
| 674 |
"""Create infographic with custom dimensions"""
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
return canvas
|
| 679 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
def generate_infographic_background(job_id: str):
|
| 681 |
"""Background task with style and size options"""
|
| 682 |
try:
|
|
@@ -684,7 +895,6 @@ def generate_infographic_background(job_id: str):
|
|
| 684 |
request = InfographicRequest(**job_data["request"])
|
| 685 |
project_id = request.project_id or f"infographic_{int(time.time())}"
|
| 686 |
|
| 687 |
-
# Get dimensions for infographic (usually story/portrait)
|
| 688 |
width, height = get_image_dimensions(
|
| 689 |
request.image_size,
|
| 690 |
request.custom_width,
|
|
@@ -715,14 +925,13 @@ def generate_infographic_background(job_id: str):
|
|
| 715 |
update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating {item.name}")
|
| 716 |
|
| 717 |
try:
|
| 718 |
-
# Use the enhanced generation with style
|
| 719 |
image = generate_image_with_settings(
|
| 720 |
item.prompt,
|
| 721 |
"dreamshaper-8",
|
| 722 |
-
"realistic",
|
| 723 |
i + 1,
|
| 724 |
None,
|
| 725 |
-
768, 1024
|
| 726 |
)
|
| 727 |
generated_images.append(image)
|
| 728 |
|
|
@@ -775,7 +984,7 @@ def generate_infographic_background(job_id: str):
|
|
| 775 |
"image_size": f"{width}x{height}"
|
| 776 |
}
|
| 777 |
|
| 778 |
-
update_job_status(job_id, JobStatus.COMPLETED, 100,
|
| 779 |
|
| 780 |
except Exception as e:
|
| 781 |
update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
|
|
@@ -788,7 +997,7 @@ def generate_infographic_background(job_id: str):
|
|
| 788 |
async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
|
| 789 |
"""Main endpoint with style and size options"""
|
| 790 |
try:
|
| 791 |
-
print(f"📥 Received storybook request - Style: {request.get('style', 'childrens_book')}
|
| 792 |
|
| 793 |
if 'consistency_seed' not in request:
|
| 794 |
request['consistency_seed'] = random.randint(1000, 9999)
|
|
@@ -803,7 +1012,6 @@ async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
|
|
| 803 |
job_id = create_job(story_request)
|
| 804 |
background_tasks.add_task(generate_storybook_background, job_id)
|
| 805 |
|
| 806 |
-
# Get dimensions for response
|
| 807 |
width, height = get_image_dimensions(
|
| 808 |
request.get('image_size', 'portrait'),
|
| 809 |
request.get('custom_width'),
|
|
@@ -826,7 +1034,7 @@ async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
|
|
| 826 |
async def generate_infographic(request: dict, background_tasks: BackgroundTasks):
|
| 827 |
"""Generate infographic with style and size options"""
|
| 828 |
try:
|
| 829 |
-
print(f"📥 Received infographic request - Style: {request.get('style', 'food_photography')}
|
| 830 |
|
| 831 |
if 'project_id' not in request:
|
| 832 |
request['project_id'] = f"infographic_{int(time.time())}"
|
|
@@ -837,7 +1045,6 @@ async def generate_infographic(request: dict, background_tasks: BackgroundTasks)
|
|
| 837 |
job_id = create_job(infographic_request)
|
| 838 |
background_tasks.add_task(generate_infographic_background, job_id)
|
| 839 |
|
| 840 |
-
# Get dimensions for response
|
| 841 |
width, height = get_image_dimensions(
|
| 842 |
request.get('image_size', 'story'),
|
| 843 |
request.get('custom_width'),
|
|
@@ -922,13 +1129,11 @@ def test_generation(prompt, model_choice, style, size_preset, custom_width, cust
|
|
| 922 |
if not prompt.strip():
|
| 923 |
return None, "❌ Please enter a prompt"
|
| 924 |
try:
|
| 925 |
-
# Get dimensions
|
| 926 |
if size_preset == "custom" and custom_width and custom_height:
|
| 927 |
width, height = custom_width, custom_height
|
| 928 |
else:
|
| 929 |
width, height = SIZE_PRESETS.get(size_preset, (768, 1024))
|
| 930 |
|
| 931 |
-
# Generate image
|
| 932 |
image = generate_image_with_settings(
|
| 933 |
prompt, model_choice, style, 1, None, width, height
|
| 934 |
)
|
|
|
|
| 98 |
FAILED = "failed"
|
| 99 |
|
| 100 |
# =============================================
|
| 101 |
+
# Style and Size Configuration
|
| 102 |
# =============================================
|
| 103 |
|
| 104 |
# Available image styles with their prompts
|
|
|
|
| 185 |
}
|
| 186 |
}
|
| 187 |
|
| 188 |
+
# Size presets with valid dimensions (divisible by 8)
|
| 189 |
SIZE_PRESETS = {
|
| 190 |
"square": {"width": 768, "height": 768, "description": "1:1 Square"},
|
| 191 |
"portrait": {"width": 768, "height": 1024, "description": "3:4 Portrait"},
|
|
|
|
| 194 |
"ultrawide": {"width": 1920, "height": 768, "description": "2.5:1 Ultrawide"},
|
| 195 |
"instagram": {"width": 1080, "height": 1080, "description": "1:1 Instagram"},
|
| 196 |
"story": {"width": 1080, "height": 1920, "description": "9:16 Story"},
|
| 197 |
+
"facebook": {"width": 1200, "height": 632, "description": "1.91:1 Facebook"},
|
| 198 |
+
"twitter": {"width": 1600, "height": 896, "description": "16:9 Twitter"},
|
| 199 |
"custom": {"width": 768, "height": 1024, "description": "Custom Size"}
|
| 200 |
}
|
| 201 |
|
|
|
|
| 214 |
characters: List[CharacterDescription] = []
|
| 215 |
model_choice: str = "dreamshaper-8"
|
| 216 |
style: str = "childrens_book"
|
| 217 |
+
image_size: str = "portrait"
|
| 218 |
+
custom_width: Optional[int] = None
|
| 219 |
+
custom_height: Optional[int] = None
|
| 220 |
callback_url: Optional[str] = None
|
| 221 |
consistency_seed: Optional[int] = None
|
| 222 |
project_id: Optional[str] = None
|
|
|
|
| 230 |
title: str
|
| 231 |
items: List[InfographicItem]
|
| 232 |
style: str = "food_photography"
|
| 233 |
+
image_size: str = "story"
|
| 234 |
custom_width: Optional[int] = None
|
| 235 |
custom_height: Optional[int] = None
|
| 236 |
project_id: Optional[str] = None
|
|
|
|
| 275 |
model_lock = threading.Lock()
|
| 276 |
|
| 277 |
# =============================================
|
| 278 |
+
# HELPER FUNCTIONS WITH DIMENSION VALIDATION
|
| 279 |
# =============================================
|
| 280 |
|
| 281 |
+
def validate_dimensions(width, height):
|
| 282 |
+
"""Ensure dimensions are compatible with Stable Diffusion (divisible by 8)"""
|
| 283 |
+
# SD models require dimensions divisible by 8
|
| 284 |
+
width = (width // 8) * 8
|
| 285 |
+
height = (height // 8) * 8
|
| 286 |
+
|
| 287 |
+
# Minimum and maximum constraints
|
| 288 |
+
width = max(256, min(1024, width))
|
| 289 |
+
height = max(256, min(1024, height))
|
| 290 |
+
|
| 291 |
+
return width, height
|
| 292 |
+
|
| 293 |
def get_image_dimensions(size_preset, custom_width=None, custom_height=None):
|
| 294 |
+
"""Get image dimensions based on preset or custom values with validation"""
|
| 295 |
if size_preset == "custom" and custom_width and custom_height:
|
| 296 |
+
width, height = custom_width, custom_height
|
| 297 |
elif size_preset in SIZE_PRESETS:
|
| 298 |
preset = SIZE_PRESETS[size_preset]
|
| 299 |
+
width, height = preset["width"], preset["height"]
|
| 300 |
else:
|
| 301 |
# Default to portrait
|
| 302 |
+
width, height = 768, 1024
|
| 303 |
+
|
| 304 |
+
# Validate dimensions
|
| 305 |
+
width, height = validate_dimensions(width, height)
|
| 306 |
+
return width, height
|
| 307 |
|
| 308 |
def get_style_config(style_name):
|
| 309 |
"""Get style configuration or return default"""
|
| 310 |
if style_name in STYLE_CONFIGS:
|
| 311 |
return STYLE_CONFIGS[style_name]
|
|
|
|
| 312 |
return STYLE_CONFIGS["childrens_book"]
|
| 313 |
|
| 314 |
def enhance_prompt_with_style(scene_visual, style="childrens_book", custom_style_prompt=None):
|
| 315 |
"""Enhanced prompt with style configuration"""
|
|
|
|
|
|
|
| 316 |
if custom_style_prompt:
|
| 317 |
style_prefix = custom_style_prompt
|
| 318 |
else:
|
| 319 |
style_config = get_style_config(style)
|
| 320 |
style_prefix = style_config["prompt_prefix"]
|
| 321 |
|
|
|
|
| 322 |
enhanced_prompt = f"{style_prefix}, {scene_visual}"
|
|
|
|
|
|
|
| 323 |
negative_prompt = style_config.get("negative", "blurry, low quality, bad anatomy, deformed, ugly")
|
| 324 |
|
| 325 |
return enhanced_prompt, negative_prompt
|
|
|
|
| 328 |
width=768, height=1024, custom_style_prompt=None):
|
| 329 |
"""Generate image with customizable style and dimensions"""
|
| 330 |
|
| 331 |
+
# Validate dimensions before generation
|
| 332 |
+
width, height = validate_dimensions(width, height)
|
| 333 |
+
|
| 334 |
# Get enhanced prompt with style
|
| 335 |
enhanced_prompt, negative_prompt = enhance_prompt_with_style(prompt, style, custom_style_prompt)
|
| 336 |
|
|
|
|
| 343 |
try:
|
| 344 |
pipe = load_model(model_choice)
|
| 345 |
|
| 346 |
+
if pipe is None:
|
| 347 |
+
raise Exception("Model pipeline not loaded")
|
| 348 |
+
|
| 349 |
+
generator = torch.Generator(device="cpu")
|
| 350 |
+
generator.manual_seed(scene_seed)
|
| 351 |
+
|
| 352 |
image = pipe(
|
| 353 |
prompt=enhanced_prompt,
|
| 354 |
negative_prompt=negative_prompt,
|
|
|
|
| 356 |
guidance_scale=7.5,
|
| 357 |
width=width,
|
| 358 |
height=height,
|
| 359 |
+
generator=generator
|
| 360 |
).images[0]
|
| 361 |
|
| 362 |
+
print(f"✅ Generated image - Style: {style}, Size: {width}x{height}, Seed: {scene_seed}")
|
| 363 |
return image
|
| 364 |
|
| 365 |
except Exception as e:
|
| 366 |
print(f"❌ Generation failed: {str(e)}")
|
| 367 |
+
print(f" - Style: {style}")
|
| 368 |
+
print(f" - Size: {width}x{height}")
|
| 369 |
raise
|
| 370 |
|
| 371 |
# =============================================
|
| 372 |
+
# DATASET FUNCTIONS
|
| 373 |
# =============================================
|
| 374 |
|
| 375 |
def ensure_dataset_exists():
|
|
|
|
| 427 |
print(f"❌ Failed to upload: {e}")
|
| 428 |
return None
|
| 429 |
|
| 430 |
+
# =============================================
|
| 431 |
+
# MEMORY MANAGEMENT
|
| 432 |
+
# =============================================
|
| 433 |
+
|
| 434 |
def get_memory_usage():
|
| 435 |
"""Get current memory usage statistics"""
|
| 436 |
process = psutil.Process()
|
|
|
|
| 506 |
load_model("dreamshaper-8")
|
| 507 |
print("✅ Model loaded and ready!")
|
| 508 |
|
| 509 |
+
# =============================================
|
| 510 |
+
# LOCAL FILE MANAGEMENT
|
| 511 |
+
# =============================================
|
| 512 |
+
|
| 513 |
def save_image_to_local(image, prompt, style="test"):
|
| 514 |
"""Save image to local storage"""
|
| 515 |
try:
|
|
|
|
| 577 |
except Exception as e:
|
| 578 |
return []
|
| 579 |
|
| 580 |
+
# =============================================
|
| 581 |
+
# JOB MANAGEMENT
|
| 582 |
+
# =============================================
|
| 583 |
+
|
| 584 |
def create_job(request) -> str:
|
| 585 |
job_id = str(uuid.uuid4())
|
| 586 |
job_storage[job_id] = {
|
|
|
|
| 611 |
return True
|
| 612 |
|
| 613 |
# =============================================
|
| 614 |
+
# STORYBOOK GENERATION
|
| 615 |
# =============================================
|
| 616 |
|
| 617 |
def generate_storybook_background(job_id: str):
|
|
|
|
| 622 |
|
| 623 |
project_id = story_request.project_id or story_request.story_title.replace(' ', '_').lower()
|
| 624 |
|
|
|
|
| 625 |
width, height = get_image_dimensions(
|
| 626 |
story_request.image_size,
|
| 627 |
story_request.custom_width,
|
| 628 |
story_request.custom_height
|
| 629 |
)
|
| 630 |
|
| 631 |
+
print(f"🎬 Starting storybook - Style: {story_request.style}, Size: {width}x{height}")
|
| 632 |
|
| 633 |
update_job_status(job_id, JobStatus.PROCESSING, 5, "Starting generation...")
|
| 634 |
|
|
|
|
| 641 |
update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating page {i+1}/{total_scenes}")
|
| 642 |
|
| 643 |
try:
|
|
|
|
| 644 |
image = generate_image_with_settings(
|
| 645 |
scene.visual,
|
| 646 |
story_request.model_choice,
|
|
|
|
| 690 |
update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
|
| 691 |
|
| 692 |
# =============================================
|
| 693 |
+
# INFOGRAPHIC LAYOUT GENERATOR
|
| 694 |
# =============================================
|
| 695 |
|
| 696 |
class InfographicLayoutGenerator:
|
|
|
|
|
|
|
| 697 |
def __init__(self):
|
| 698 |
self.title_color = (60, 60, 60)
|
| 699 |
self.text_color = (40, 40, 40)
|
| 700 |
self.accent_color = (200, 160, 120)
|
| 701 |
|
| 702 |
+
def get_font(self, size, bold=False):
|
| 703 |
+
"""Simple font loading"""
|
| 704 |
+
try:
|
| 705 |
+
return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size)
|
| 706 |
+
except:
|
| 707 |
+
try:
|
| 708 |
+
return ImageFont.truetype("arial.ttf", size)
|
| 709 |
+
except:
|
| 710 |
+
return ImageFont.load_default()
|
| 711 |
+
|
| 712 |
+
def smart_remove_background(self, image):
|
| 713 |
+
"""Simple background removal"""
|
| 714 |
+
try:
|
| 715 |
+
if image.mode != 'RGBA':
|
| 716 |
+
image = image.convert('RGBA')
|
| 717 |
+
|
| 718 |
+
data = image.getdata()
|
| 719 |
+
new_data = []
|
| 720 |
+
|
| 721 |
+
width, height = image.size
|
| 722 |
+
|
| 723 |
+
# Sample edges
|
| 724 |
+
edge_samples = []
|
| 725 |
+
for x in range(0, width, 10):
|
| 726 |
+
if x < len(data):
|
| 727 |
+
edge_samples.append(data[x])
|
| 728 |
+
for x in range(0, width, 10):
|
| 729 |
+
idx = (height-1) * width + x
|
| 730 |
+
if idx < len(data):
|
| 731 |
+
edge_samples.append(data[idx])
|
| 732 |
+
|
| 733 |
+
if not edge_samples:
|
| 734 |
+
return image
|
| 735 |
+
|
| 736 |
+
avg_r = sum(s[0] for s in edge_samples) // len(edge_samples)
|
| 737 |
+
avg_g = sum(s[1] for s in edge_samples) // len(edge_samples)
|
| 738 |
+
avg_b = sum(s[2] for s in edge_samples) // len(edge_samples)
|
| 739 |
+
|
| 740 |
+
threshold = 60
|
| 741 |
+
|
| 742 |
+
for item in data:
|
| 743 |
+
distance = ((item[0] - avg_r) ** 2 +
|
| 744 |
+
(item[1] - avg_g) ** 2 +
|
| 745 |
+
(item[2] - avg_b) ** 2) ** 0.5
|
| 746 |
+
|
| 747 |
+
if distance < threshold:
|
| 748 |
+
new_data.append((255, 255, 255, 0))
|
| 749 |
+
else:
|
| 750 |
+
new_data.append(item)
|
| 751 |
+
|
| 752 |
+
image.putdata(new_data)
|
| 753 |
+
return image
|
| 754 |
+
|
| 755 |
+
except Exception as e:
|
| 756 |
+
return image
|
| 757 |
+
|
| 758 |
+
def create_responsive_layout(self, num_items):
|
| 759 |
+
"""Calculate responsive layout"""
|
| 760 |
+
width = 900
|
| 761 |
+
height = 1600
|
| 762 |
+
|
| 763 |
+
if num_items <= 3:
|
| 764 |
+
icon_size = 180
|
| 765 |
+
columns = 1
|
| 766 |
+
rows = num_items
|
| 767 |
+
elif num_items <= 6:
|
| 768 |
+
icon_size = 150
|
| 769 |
+
columns = 2
|
| 770 |
+
rows = (num_items + 1) // 2
|
| 771 |
+
else:
|
| 772 |
+
icon_size = 120
|
| 773 |
+
columns = 2
|
| 774 |
+
rows = (num_items + 1) // 2
|
| 775 |
+
|
| 776 |
+
positions = []
|
| 777 |
+
available_height = height - 300
|
| 778 |
+
start_y = 200
|
| 779 |
+
|
| 780 |
+
for i in range(num_items):
|
| 781 |
+
col = i % columns
|
| 782 |
+
row = i // columns
|
| 783 |
+
|
| 784 |
+
if columns == 1:
|
| 785 |
+
x = (width - icon_size) // 2
|
| 786 |
+
else:
|
| 787 |
+
if col == 0:
|
| 788 |
+
x = width // 4 - icon_size // 2
|
| 789 |
+
else:
|
| 790 |
+
x = 3 * width // 4 - icon_size // 2
|
| 791 |
+
|
| 792 |
+
if rows > 1:
|
| 793 |
+
y = start_y + (row * (available_height // rows))
|
| 794 |
+
else:
|
| 795 |
+
y = start_y + (available_height - icon_size) // 2
|
| 796 |
+
|
| 797 |
+
positions.append((x, y, icon_size))
|
| 798 |
+
|
| 799 |
+
return positions, icon_size, columns
|
| 800 |
+
|
| 801 |
def create_food_infographic(self, title, items_with_prompts, generated_images, background_image=None, width=900, height=1600):
|
| 802 |
"""Create infographic with custom dimensions"""
|
| 803 |
+
if background_image:
|
| 804 |
+
bg = background_image.resize((width, height), Image.Resampling.LANCZOS)
|
| 805 |
+
canvas = bg.convert('RGB')
|
| 806 |
+
else:
|
| 807 |
+
canvas = Image.new('RGB', (width, height), (255, 255, 255))
|
| 808 |
+
|
| 809 |
+
draw = ImageDraw.Draw(canvas)
|
| 810 |
+
num_items = len(items_with_prompts)
|
| 811 |
+
positions, icon_size, columns = self.create_responsive_layout(num_items)
|
| 812 |
+
|
| 813 |
+
# Draw title
|
| 814 |
+
title_font = self.get_font(48, bold=True)
|
| 815 |
+
title_bbox = draw.textbbox((0, 0), title, font=title_font)
|
| 816 |
+
title_width = title_bbox[2] - title_bbox[0]
|
| 817 |
+
title_x = (width - title_width) // 2
|
| 818 |
+
title_y = 60
|
| 819 |
+
|
| 820 |
+
draw.rounded_rectangle(
|
| 821 |
+
[title_x-20, title_y-10, title_x+title_width+20, title_y+60],
|
| 822 |
+
radius=20, fill=(250, 250, 250), outline=None
|
| 823 |
+
)
|
| 824 |
+
draw.text((title_x, title_y), title, font=title_font, fill=self.title_color)
|
| 825 |
+
|
| 826 |
+
# Draw subtitle
|
| 827 |
+
subtitle_font = self.get_font(24)
|
| 828 |
+
subtitle = "Healthy Foods · 健康食品"
|
| 829 |
+
subtitle_bbox = draw.textbbox((0, 0), subtitle, font=subtitle_font)
|
| 830 |
+
subtitle_width = subtitle_bbox[2] - subtitle_bbox[0]
|
| 831 |
+
subtitle_x = (width - subtitle_width) // 2
|
| 832 |
+
subtitle_y = title_y + 60
|
| 833 |
+
draw.text((subtitle_x, subtitle_y), subtitle, font=subtitle_font, fill=(120, 120, 120))
|
| 834 |
+
|
| 835 |
+
# Place items
|
| 836 |
+
for i, ((food_name, _), img) in enumerate(zip(items_with_prompts, generated_images)):
|
| 837 |
+
if i >= len(positions):
|
| 838 |
+
break
|
| 839 |
+
|
| 840 |
+
x, y, size = positions[i]
|
| 841 |
+
|
| 842 |
+
if img:
|
| 843 |
+
icon_img = self.smart_remove_background(img)
|
| 844 |
+
icon_img.thumbnail((size, size), Image.Resampling.LANCZOS)
|
| 845 |
+
|
| 846 |
+
mask = Image.new('L', (size, size), 0)
|
| 847 |
+
mask_draw = ImageDraw.Draw(mask)
|
| 848 |
+
mask_draw.ellipse((0, 0, size, size), fill=255)
|
| 849 |
+
|
| 850 |
+
icon_final = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
| 851 |
+
paste_x = (size - icon_img.width) // 2
|
| 852 |
+
paste_y = (size - icon_img.height) // 2
|
| 853 |
+
icon_final.paste(icon_img, (paste_x, paste_y), icon_img)
|
| 854 |
+
|
| 855 |
+
circle_bg = Image.new('RGBA', (size+20, size+20), (0, 0, 0, 0))
|
| 856 |
+
circle_draw = ImageDraw.Draw(circle_bg)
|
| 857 |
+
circle_draw.ellipse([5, 5, size+15, size+15], fill=(245, 245, 245), outline=None)
|
| 858 |
+
canvas.paste(circle_bg, (x-10, y-10), circle_bg)
|
| 859 |
+
canvas.paste(icon_final, (x, y), icon_final)
|
| 860 |
+
|
| 861 |
+
name_font = self.get_font(28 if num_items > 6 else 32, bold=True)
|
| 862 |
+
|
| 863 |
+
if columns == 1:
|
| 864 |
+
name_x = x + (size // 2)
|
| 865 |
+
name_y = y + size + 15
|
| 866 |
+
draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color, anchor="mm")
|
| 867 |
+
number_font = self.get_font(20)
|
| 868 |
+
draw.text((name_x, name_y + 35), f"#{i+1:02d}", font=number_font, fill=(150, 150, 150), anchor="mm")
|
| 869 |
+
else:
|
| 870 |
+
name_x = x + size + 15
|
| 871 |
+
name_y = y + (size // 2) - 20
|
| 872 |
+
draw.text((name_x, name_y), food_name, font=name_font, fill=self.text_color)
|
| 873 |
+
number_font = self.get_font(18)
|
| 874 |
+
draw.text((name_x, name_y + 30), f"#{i+1:02d}", font=number_font, fill=(150, 150, 150))
|
| 875 |
+
|
| 876 |
+
# Draw footer
|
| 877 |
+
footer_font = self.get_font(20)
|
| 878 |
+
footer_text = "· Fresh & Healthy ·"
|
| 879 |
+
footer_bbox = draw.textbbox((0, 0), footer_text, font=footer_font)
|
| 880 |
+
footer_width = footer_bbox[2] - footer_bbox[0]
|
| 881 |
+
footer_x = (width - footer_width) // 2
|
| 882 |
+
footer_y = height - 50
|
| 883 |
+
draw.text((footer_x, footer_y), footer_text, font=footer_font, fill=(150, 150, 150))
|
| 884 |
+
|
| 885 |
return canvas
|
| 886 |
|
| 887 |
+
# =============================================
|
| 888 |
+
# INFOGRAPHIC GENERATION
|
| 889 |
+
# =============================================
|
| 890 |
+
|
| 891 |
def generate_infographic_background(job_id: str):
|
| 892 |
"""Background task with style and size options"""
|
| 893 |
try:
|
|
|
|
| 895 |
request = InfographicRequest(**job_data["request"])
|
| 896 |
project_id = request.project_id or f"infographic_{int(time.time())}"
|
| 897 |
|
|
|
|
| 898 |
width, height = get_image_dimensions(
|
| 899 |
request.image_size,
|
| 900 |
request.custom_width,
|
|
|
|
| 925 |
update_job_status(job_id, JobStatus.PROCESSING, progress, f"Generating {item.name}")
|
| 926 |
|
| 927 |
try:
|
|
|
|
| 928 |
image = generate_image_with_settings(
|
| 929 |
item.prompt,
|
| 930 |
"dreamshaper-8",
|
| 931 |
+
"realistic",
|
| 932 |
i + 1,
|
| 933 |
None,
|
| 934 |
+
768, 1024
|
| 935 |
)
|
| 936 |
generated_images.append(image)
|
| 937 |
|
|
|
|
| 984 |
"image_size": f"{width}x{height}"
|
| 985 |
}
|
| 986 |
|
| 987 |
+
update_job_status(job_id, JobStatus.COMPLETED, 100, "Completed!", result)
|
| 988 |
|
| 989 |
except Exception as e:
|
| 990 |
update_job_status(job_id, JobStatus.FAILED, 0, f"Failed: {str(e)}")
|
|
|
|
| 997 |
async def generate_storybook(request: dict, background_tasks: BackgroundTasks):
|
| 998 |
"""Main endpoint with style and size options"""
|
| 999 |
try:
|
| 1000 |
+
print(f"📥 Received storybook request - Style: {request.get('style', 'childrens_book')}")
|
| 1001 |
|
| 1002 |
if 'consistency_seed' not in request:
|
| 1003 |
request['consistency_seed'] = random.randint(1000, 9999)
|
|
|
|
| 1012 |
job_id = create_job(story_request)
|
| 1013 |
background_tasks.add_task(generate_storybook_background, job_id)
|
| 1014 |
|
|
|
|
| 1015 |
width, height = get_image_dimensions(
|
| 1016 |
request.get('image_size', 'portrait'),
|
| 1017 |
request.get('custom_width'),
|
|
|
|
| 1034 |
async def generate_infographic(request: dict, background_tasks: BackgroundTasks):
|
| 1035 |
"""Generate infographic with style and size options"""
|
| 1036 |
try:
|
| 1037 |
+
print(f"📥 Received infographic request - Style: {request.get('style', 'food_photography')}")
|
| 1038 |
|
| 1039 |
if 'project_id' not in request:
|
| 1040 |
request['project_id'] = f"infographic_{int(time.time())}"
|
|
|
|
| 1045 |
job_id = create_job(infographic_request)
|
| 1046 |
background_tasks.add_task(generate_infographic_background, job_id)
|
| 1047 |
|
|
|
|
| 1048 |
width, height = get_image_dimensions(
|
| 1049 |
request.get('image_size', 'story'),
|
| 1050 |
request.get('custom_width'),
|
|
|
|
| 1129 |
if not prompt.strip():
|
| 1130 |
return None, "❌ Please enter a prompt"
|
| 1131 |
try:
|
|
|
|
| 1132 |
if size_preset == "custom" and custom_width and custom_height:
|
| 1133 |
width, height = custom_width, custom_height
|
| 1134 |
else:
|
| 1135 |
width, height = SIZE_PRESETS.get(size_preset, (768, 1024))
|
| 1136 |
|
|
|
|
| 1137 |
image = generate_image_with_settings(
|
| 1138 |
prompt, model_choice, style, 1, None, width, height
|
| 1139 |
)
|