yukee1992 commited on
Commit
f2a8121
Β·
verified Β·
1 Parent(s): 1bd1797

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -93
app.py CHANGED
@@ -12,9 +12,12 @@ 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
 
 
 
18
 
19
  # External OCI API URL
20
  OCI_API_BASE_URL = "https://yukee1992-oci-story-book.hf.space"
@@ -66,6 +69,26 @@ current_pipe = None
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, current_pipe
@@ -98,6 +121,7 @@ def load_model(model_name="dreamshaper-8"):
98
  current_model_name = model_name
99
 
100
  print(f"βœ… Model loaded and cached: {model_name}")
 
101
  return pipe
102
 
103
  except Exception as e:
@@ -117,56 +141,54 @@ def load_model(model_name="dreamshaper-8"):
117
  print("πŸš€ Initializing Storybook Generator...")
118
  current_pipe = load_model("dreamshaper-8")
119
  print("βœ… Default model loaded and ready!")
 
120
 
121
- # PROFESSIONAL PROMPT ENGINEERING
122
- def enhance_prompt(prompt, style="childrens_book"):
123
- """Transform basic prompts into professional-grade prompts"""
124
 
125
- style_templates = {
126
- "childrens_book": [
127
- "masterpiece, best quality, 4K, ultra detailed, children's book illustration",
128
- "watercolor painting, whimsical, cute, charming, storybook style",
129
- "vibrant colors, soft lighting, magical, enchanting, dreamlike",
130
- "Pixar style, Disney animation, high detail, professional artwork"
131
- ],
132
- "realistic": [
133
- "photorealistic, 8K, ultra detailed, professional photography",
134
- "sharp focus, studio lighting, high resolution, intricate details",
135
- "realistic textures, natural lighting, cinematic quality"
136
- ],
137
- "fantasy": [
138
- "epic fantasy art, digital painting, concept art, trending on artstation",
139
- "magical, mystical, ethereal, otherworldly, fantasy illustration",
140
- "dynamic composition, dramatic lighting, highly detailed"
141
- ],
142
- "anime": [
143
- "anime style, Japanese animation, high quality, detailed artwork",
144
- "beautiful anime illustration, vibrant colors, clean lines",
145
- "studio ghibli style, makoto shinkai, professional anime art"
146
- ]
147
  }
148
 
149
- templates = style_templates.get(style, style_templates["childrens_book"])
150
- style_prompt = templates[0]
151
 
152
- enhanced = f"{style_prompt}, {prompt}"
 
153
 
154
- quality_boosters = [
155
- "intricate details", "beautiful composition", "perfect lighting",
156
- "professional artwork", "award winning", "trending on artstation"
157
  ]
158
 
159
- boosters = random.sample(quality_boosters, 2)
160
- enhanced += ", " + ", ".join(boosters)
 
161
 
 
162
  negative_prompt = (
163
  "blurry, low quality, low resolution, ugly, deformed, poorly drawn, "
164
  "bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, "
165
- "disconnected limbs, mutation, mutated, ugly, disgusting, bad art, "
166
- "beginner, amateur, distorted, watermark, signature, text, username"
 
 
167
  )
168
 
169
- return enhanced, negative_prompt
170
 
171
  def save_complete_storybook_page(image, story_title, sequence_number, scene_text):
172
  """Save image AND text to OCI with organized structure"""
@@ -204,67 +226,64 @@ def save_complete_storybook_page(image, story_title, sequence_number, scene_text
204
  except Exception as e:
205
  return f"❌ Save failed: {str(e)}"
206
 
207
- def enhance_with_character_context(scene_visual, story_title, characters):
208
- """Add character descriptions to maintain consistency"""
209
- if characters:
210
- character_context = " ".join([f"{char.name}: {char.description}" for char in characters])
211
- return f"Character descriptions: {character_context}. {scene_visual}"
212
- return scene_visual
213
-
214
- def get_character_seed(story_title, character_name):
215
- """Get consistent seed for character generation"""
216
  if story_title not in character_seeds:
217
  character_seeds[story_title] = {}
218
 
219
- if character_name not in character_seeds[story_title]:
220
- # Generate a stable seed based on character name and story title
221
- seed_value = hash(f"{story_title}_{character_name}") % 1000000
222
- character_seeds[story_title][character_name] = seed_value
223
- print(f"🌱 Seed for {character_name}: {seed_value}")
 
 
 
 
 
224
 
225
- return character_seeds[story_title][character_name]
226
 
227
  def generate_storybook_page(scene_visual, story_title, sequence_number, scene_text, characters, model_choice="dreamshaper-8", style="childrens_book"):
228
- """Generate a storybook page with character consistency"""
229
  global current_pipe, current_model_name
230
 
231
  try:
232
- # Switch model if different from current - BUT DON'T RELOAD UNLESS NECESSARY
233
  if model_choice != current_model_name:
234
  print(f"πŸ”„ Switching to model: {model_choice}")
235
- current_pipe = load_model(model_choice) # This uses cached version if available
236
-
237
- # ENHANCE PROMPT WITH CHARACTER CONTEXT
238
- enhanced_visual = enhance_with_character_context(scene_visual, story_title, characters)
239
 
240
- # Add scene continuity context
241
- if sequence_number > 1:
242
- enhanced_visual = f"Scene {sequence_number}, maintain character consistency from previous scenes. {enhanced_visual}"
243
-
244
- enhanced_prompt, negative_prompt = enhance_prompt(enhanced_visual, style)
245
 
246
  print(f"πŸ“– Generating page {sequence_number} for: {story_title}")
 
247
  if characters:
248
  print(f"πŸ‘€ Characters: {[char.name for char in characters]}")
249
 
250
- # Use consistent seed for character generation
251
  generator = torch.Generator(device="cpu")
 
252
  if characters:
253
- # Use seed from main character for consistency
254
- main_char_seed = get_character_seed(story_title, characters[0].name)
255
  generator.manual_seed(main_char_seed)
256
- print(f"🌱 Using seed {main_char_seed} for character consistency")
257
  else:
258
- seed = int(time.time())
259
- generator.manual_seed(seed)
260
- print(f"🌱 Using timestamp seed {seed}")
 
261
 
262
- # Generate high-quality image - USE THE GLOBAL current_pipe
263
  image = current_pipe(
264
  prompt=enhanced_prompt,
265
  negative_prompt=negative_prompt,
266
- num_inference_steps=30,
267
- guidance_scale=8.5,
268
  width=768,
269
  height=768,
270
  generator=generator
@@ -279,7 +298,7 @@ def generate_storybook_page(scene_visual, story_title, sequence_number, scene_te
279
  return None, f"❌ Generation failed: {str(e)}"
280
 
281
  def batch_generate_complete_storybook(story_title, scenes_data, characters, model_choice="dreamshaper-8", style="childrens_book"):
282
- """Generate complete storybook with images and text - MODEL LOADS ONLY ONCE"""
283
  global character_descriptions, current_pipe
284
 
285
  results = []
@@ -290,40 +309,63 @@ def batch_generate_complete_storybook(story_title, scenes_data, characters, mode
290
  print(f"πŸ‘€ Characters: {len(characters)}")
291
  print(f"🎨 Using model: {model_choice}")
292
 
 
 
 
293
  # Store character descriptions for this story
294
  if characters:
295
  character_descriptions[story_title] = characters
296
  print(f"βœ… Character context stored for {story_title}")
297
 
298
- # Load model once at the beginning - THIS IS THE KEY FIX
299
  print(f"πŸ”§ Loading model for this storybook...")
300
  current_pipe = load_model(model_choice)
301
 
302
  start_time = time.time()
303
 
304
  for i, scene_data in enumerate(scenes_data, 1):
305
- scene_visual = scene_data.get('visual', '')
306
- scene_text = scene_data.get('text', '')
307
-
308
- print(f"πŸ”„ Generating page {i}/{len(scenes_data)}...")
309
- image, status = generate_storybook_page(
310
- scene_visual, story_title, i, scene_text, characters, model_choice, style
311
- )
312
-
313
- if image:
314
- results.append((f"Page {i}", image, scene_text))
315
- status_messages.append(f"Page {i}: {status}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
  total_time = time.time() - start_time
 
 
318
  print(f"βœ… Batch generation completed in {total_time:.2f} seconds")
319
- print(f"πŸ“Š Average: {total_time/len(scenes_data):.2f} seconds per page")
320
 
321
  return results, "\n".join(status_messages)
322
 
323
  # FastAPI endpoint for n8n
324
  @app.post("/api/generate-storybook")
325
  async def api_generate_storybook(request: StorybookRequest):
326
- """API endpoint for n8n automation - OPTIMIZED with character consistency"""
327
  try:
328
  print(f"πŸ“š Received storybook request: {request.story_title}")
329
  print(f"πŸ“– Pages to generate: {len(request.scenes)}")
@@ -331,14 +373,14 @@ async def api_generate_storybook(request: StorybookRequest):
331
 
332
  if request.characters:
333
  for char in request.characters:
334
- print(f" - {char.name}: {char.description[:50]}...")
335
 
336
  start_time = time.time()
337
 
338
  # Convert to scene data format
339
  scenes_data = [{"visual": scene.visual, "text": scene.text} for scene in request.scenes]
340
 
341
- # Generate storybook (model loads only once)
342
  results, status = batch_generate_complete_storybook(
343
  request.story_title,
344
  scenes_data,
@@ -374,20 +416,23 @@ async def api_generate_storybook(request: StorybookRequest):
374
  traceback.print_exc()
375
  raise HTTPException(status_code=500, detail=error_msg)
376
 
377
- # Health check endpoint
378
  @app.get("/api/health")
379
  async def health_check():
 
380
  return {
381
  "status": "healthy",
382
  "service": "Storybook Generator API",
383
  "timestamp": datetime.now().isoformat(),
 
384
  "models_loaded": list(model_cache.keys()),
385
  "current_model": current_model_name,
386
  "cached_models_count": len(model_cache),
387
- "stories_tracked": len(character_descriptions)
 
388
  }
389
 
390
- # Gradio Interface Functions (simplified)
391
  def generate_single_page(prompt, story_title, scene_text, model_choice, style):
392
  """Generate a single page for Gradio interface"""
393
  if not prompt or not story_title:
 
12
  import base64
13
  import json
14
  from typing import Dict, List, Tuple, Optional
15
+ from fastapi import FastAPI, HTTPException, BackgroundTasks
16
  from pydantic import BaseModel
17
  import random
18
+ import gc
19
+ import psutil
20
+ import threading
21
 
22
  # External OCI API URL
23
  OCI_API_BASE_URL = "https://yukee1992-oci-story-book.hf.space"
 
69
  character_descriptions = {}
70
  character_seeds = {} # Store seeds for consistent character generation
71
 
72
+ # Memory monitoring function
73
+ def monitor_memory():
74
+ """Monitor current memory usage"""
75
+ try:
76
+ process = psutil.Process()
77
+ memory_usage = process.memory_info().rss / 1024 / 1024 # MB
78
+ print(f"πŸ“Š Memory usage: {memory_usage:.2f} MB")
79
+ return memory_usage
80
+ except:
81
+ print("⚠️ Could not monitor memory (psutil not available)")
82
+ return 0
83
+
84
+ # Memory cleanup function
85
+ def cleanup_memory():
86
+ """Clean up memory and cache"""
87
+ gc.collect()
88
+ if torch.cuda.is_available():
89
+ torch.cuda.empty_cache()
90
+ print("🧹 Memory cleaned up")
91
+
92
  def load_model(model_name="dreamshaper-8"):
93
  """Load model into global cache - runs only once per model"""
94
  global model_cache, current_model_name, current_pipe
 
121
  current_model_name = model_name
122
 
123
  print(f"βœ… Model loaded and cached: {model_name}")
124
+ monitor_memory()
125
  return pipe
126
 
127
  except Exception as e:
 
141
  print("πŸš€ Initializing Storybook Generator...")
142
  current_pipe = load_model("dreamshaper-8")
143
  print("βœ… Default model loaded and ready!")
144
+ monitor_memory()
145
 
146
+ # IMPROVED PROMPT ENGINEERING FOR CONSISTENCY
147
+ def enhance_prompt(scene_visual, characters, style="childrens_book", page_number=1):
148
+ """Transform basic prompts into professional-grade prompts with character consistency"""
149
 
150
+ # Character context - CRITICAL for consistency
151
+ character_context = ""
152
+ if characters:
153
+ character_context = " ".join([f"{char.description}" for char in characters])
154
+ character_context = f"Features: {character_context}. "
155
+
156
+ # Scene continuity context
157
+ continuity_context = f"Scene {page_number}, " if page_number > 1 else ""
158
+
159
+ # Style templates (less overwhelming)
160
+ style_presets = {
161
+ "childrens_book": "children's book illustration, watercolor style, whimsical, charming, vibrant colors",
162
+ "realistic": "photorealistic, professional photography, natural lighting, detailed",
163
+ "fantasy": "fantasy art, digital painting, magical, epic, concept art",
164
+ "anime": "anime style, Japanese animation, clean lines, vibrant colors"
 
 
 
 
 
 
 
165
  }
166
 
167
+ style_prompt = style_presets.get(style, style_presets["childrens_book"])
 
168
 
169
+ # Build the main prompt - SCENE VISUAL COMES FIRST for importance
170
+ main_prompt = f"{continuity_context}{scene_visual}. {character_context}{style_prompt}"
171
 
172
+ # Quality boosters (subtle)
173
+ quality_enhancers = [
174
+ "high quality", "detailed", "well composed", "good lighting"
175
  ]
176
 
177
+ # Add 1-2 quality enhancers randomly
178
+ enhancers = random.sample(quality_enhancers, min(2, len(quality_enhancers)))
179
+ main_prompt += ", " + ", ".join(enhancers)
180
 
181
+ # STRONG negative prompt to prevent deviations
182
  negative_prompt = (
183
  "blurry, low quality, low resolution, ugly, deformed, poorly drawn, "
184
  "bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, "
185
+ "disconnected limbs, mutation, mutated, disgusting, bad art, "
186
+ "beginner, amateur, distorted, watermark, signature, text, username, "
187
+ "multiple people, crowd, group, different characters, inconsistent features, "
188
+ "changed appearance, different face, altered features"
189
  )
190
 
191
+ return main_prompt, negative_prompt
192
 
193
  def save_complete_storybook_page(image, story_title, sequence_number, scene_text):
194
  """Save image AND text to OCI with organized structure"""
 
226
  except Exception as e:
227
  return f"❌ Save failed: {str(e)}"
228
 
229
+ def get_character_seed(story_title, character_name, page_number):
230
+ """Get consistent but varied seed for character generation across pages"""
 
 
 
 
 
 
 
231
  if story_title not in character_seeds:
232
  character_seeds[story_title] = {}
233
 
234
+ seed_key = f"{character_name}_{page_number}"
235
+
236
+ if seed_key not in character_seeds[story_title]:
237
+ # Generate stable but varied seed based on character, story, and page
238
+ base_seed = hash(f"{story_title}_{character_name}") % 1000000
239
+ # Add page variation but keep it consistent for same character+page
240
+ page_variation = (page_number * 13) % 1000 # Small variation per page
241
+ seed_value = (base_seed + page_variation) % 1000000
242
+ character_seeds[story_title][seed_key] = seed_value
243
+ print(f"🌱 Seed for {character_name} page {page_number}: {seed_value}")
244
 
245
+ return character_seeds[story_title][seed_key]
246
 
247
  def generate_storybook_page(scene_visual, story_title, sequence_number, scene_text, characters, model_choice="dreamshaper-8", style="childrens_book"):
248
+ """Generate a storybook page with STRONG character consistency"""
249
  global current_pipe, current_model_name
250
 
251
  try:
252
+ # Switch model if different from current
253
  if model_choice != current_model_name:
254
  print(f"πŸ”„ Switching to model: {model_choice}")
255
+ current_pipe = load_model(model_choice)
 
 
 
256
 
257
+ # ENHANCE PROMPT WITH PROPER WEIGHTING
258
+ enhanced_prompt, negative_prompt = enhance_prompt(
259
+ scene_visual, characters, style, sequence_number
260
+ )
 
261
 
262
  print(f"πŸ“– Generating page {sequence_number} for: {story_title}")
263
+ print(f"πŸ“ Prompt: {enhanced_prompt[:100]}...")
264
  if characters:
265
  print(f"πŸ‘€ Characters: {[char.name for char in characters]}")
266
 
267
+ # Use consistent seed strategy
268
  generator = torch.Generator(device="cpu")
269
+
270
  if characters:
271
+ # Use seed from main character for this specific page
272
+ main_char_seed = get_character_seed(story_title, characters[0].name, sequence_number)
273
  generator.manual_seed(main_char_seed)
274
+ print(f"🌱 Using seed {main_char_seed} for {characters[0].name} on page {sequence_number}")
275
  else:
276
+ # Still use consistent seed for non-character scenes
277
+ scene_seed = hash(f"{story_title}_{sequence_number}") % 1000000
278
+ generator.manual_seed(scene_seed)
279
+ print(f"🌱 Using scene seed {scene_seed} for page {sequence_number}")
280
 
281
+ # Generate with higher steps for better consistency
282
  image = current_pipe(
283
  prompt=enhanced_prompt,
284
  negative_prompt=negative_prompt,
285
+ num_inference_steps=35, # Increased for better detail
286
+ guidance_scale=7.5, # Slightly lower for more prompt adherence
287
  width=768,
288
  height=768,
289
  generator=generator
 
298
  return None, f"❌ Generation failed: {str(e)}"
299
 
300
  def batch_generate_complete_storybook(story_title, scenes_data, characters, model_choice="dreamshaper-8", style="childrens_book"):
301
+ """Generate complete storybook with memory management and consistency"""
302
  global character_descriptions, current_pipe
303
 
304
  results = []
 
309
  print(f"πŸ‘€ Characters: {len(characters)}")
310
  print(f"🎨 Using model: {model_choice}")
311
 
312
+ # Initial memory check
313
+ initial_memory = monitor_memory()
314
+
315
  # Store character descriptions for this story
316
  if characters:
317
  character_descriptions[story_title] = characters
318
  print(f"βœ… Character context stored for {story_title}")
319
 
320
+ # Load model once at the beginning
321
  print(f"πŸ”§ Loading model for this storybook...")
322
  current_pipe = load_model(model_choice)
323
 
324
  start_time = time.time()
325
 
326
  for i, scene_data in enumerate(scenes_data, 1):
327
+ try:
328
+ # Clean memory every 2 pages
329
+ if i % 2 == 0:
330
+ cleanup_memory()
331
+ monitor_memory()
332
+
333
+ scene_visual = scene_data.get('visual', '')
334
+ scene_text = scene_data.get('text', '')
335
+
336
+ print(f"πŸ”„ Generating page {i}/{len(scenes_data)}...")
337
+ print(f"🎬 Scene: {scene_visual[:80]}...")
338
+
339
+ image, status = generate_storybook_page(
340
+ scene_visual, story_title, i, scene_text, characters, model_choice, style
341
+ )
342
+
343
+ if image:
344
+ results.append((f"Page {i}", image, scene_text))
345
+ status_messages.append(f"Page {i}: {status}")
346
+
347
+ # Brief pause between generations
348
+ if i < len(scenes_data):
349
+ time.sleep(2)
350
+
351
+ except Exception as e:
352
+ error_msg = f"❌ Failed page {i}: {str(e)}"
353
+ print(error_msg)
354
+ status_messages.append(error_msg)
355
+ # Continue with next page instead of stopping
356
 
357
  total_time = time.time() - start_time
358
+ final_memory = monitor_memory()
359
+
360
  print(f"βœ… Batch generation completed in {total_time:.2f} seconds")
361
+ print(f"πŸ“Š Memory delta: {final_memory - initial_memory:.2f} MB")
362
 
363
  return results, "\n".join(status_messages)
364
 
365
  # FastAPI endpoint for n8n
366
  @app.post("/api/generate-storybook")
367
  async def api_generate_storybook(request: StorybookRequest):
368
+ """API endpoint for n8n automation - OPTIMIZED for consistency"""
369
  try:
370
  print(f"πŸ“š Received storybook request: {request.story_title}")
371
  print(f"πŸ“– Pages to generate: {len(request.scenes)}")
 
373
 
374
  if request.characters:
375
  for char in request.characters:
376
+ print(f" - {char.name}: {char.description}")
377
 
378
  start_time = time.time()
379
 
380
  # Convert to scene data format
381
  scenes_data = [{"visual": scene.visual, "text": scene.text} for scene in request.scenes]
382
 
383
+ # Generate storybook
384
  results, status = batch_generate_complete_storybook(
385
  request.story_title,
386
  scenes_data,
 
416
  traceback.print_exc()
417
  raise HTTPException(status_code=500, detail=error_msg)
418
 
419
+ # Health check endpoint with memory info
420
  @app.get("/api/health")
421
  async def health_check():
422
+ memory_info = monitor_memory()
423
  return {
424
  "status": "healthy",
425
  "service": "Storybook Generator API",
426
  "timestamp": datetime.now().isoformat(),
427
+ "memory_usage_mb": round(memory_info, 2),
428
  "models_loaded": list(model_cache.keys()),
429
  "current_model": current_model_name,
430
  "cached_models_count": len(model_cache),
431
+ "stories_tracked": len(character_descriptions),
432
+ "character_seeds_stored": sum(len(seeds) for seeds in character_seeds.values())
433
  }
434
 
435
+ # Gradio Interface Functions
436
  def generate_single_page(prompt, story_title, scene_text, model_choice, style):
437
  """Generate a single page for Gradio interface"""
438
  if not prompt or not story_title: