course-creator-ai / coursecrafter /tools /image_generation.py
sizzlebop's picture
Upload 35 files
94ecb74 verified
"""
🎨 Image Generation Tools
High-quality image generation using Pollinations API with gptimage model
"""
import urllib.parse
import base64
from typing import Dict, Any, Optional, List
import logging
logger = logging.getLogger(__name__)
# Import config for Pollinations settings
from ..utils.config import config
class ImageGenerator:
"""High-quality image generation using Pollinations API"""
def __init__(self):
self.base_url = "https://image.pollinations.ai/prompt"
self.image_config = config.get_image_generation_config()
# Set up default parameters from config
self.default_params = {
"width": self.image_config.get("default_width", 1280),
"height": self.image_config.get("default_height", 720),
"model": self.image_config.get("default_model", "gptimage"),
"nologo": "true",
"enhance": "true",
"private": "true"
}
# Add API credentials if available
if self.image_config.get("pollinations_api_token"):
self.default_params["token"] = self.image_config["pollinations_api_token"]
if self.image_config.get("pollinations_api_reference"):
self.default_params["referrer"] = self.image_config["pollinations_api_reference"]
async def generate_image_url(self, prompt: str, **kwargs) -> Optional[str]:
"""Generate image and return URL"""
try:
print(f"🎨 Generating image: {prompt[:50]}...")
# Merge custom params with defaults
params = {**self.default_params, **kwargs}
# Encode the prompt for URL
encoded_prompt = urllib.parse.quote(prompt)
url = f"{self.base_url}/{encoded_prompt}"
print(f"🌐 Request URL: {url}")
print(f"πŸ“‹ Parameters: {params}")
# Use requests instead of aiohttp for better compatibility
import requests
try:
response = requests.get(url, params=params, timeout=300)
response.raise_for_status() # Raise an exception for bad status codes
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"πŸ“„ Error response: {e.response.text[:200]}...")
return None
if response.status_code == 200:
image_data = response.content
# Convert to base64
base64_data = base64.b64encode(image_data).decode('utf-8')
print(f"βœ… Image data generated successfully ({len(image_data)} bytes)")
return {
"prompt": prompt,
"base64_data": base64_data,
"data_url": f"data:image/jpeg;base64,{base64_data}",
"size_bytes": len(image_data),
"params": params
}
else:
print(f"❌ Image generation failed: HTTP {response.status_code}")
print(f"πŸ“„ Response text: {response.text[:200]}...")
return None
except Exception as e:
logger.error(f"Image data generation failed: {e}")
print(f"❌ Image data generation error: {e}")
return None
async def generate_educational_image(self, lesson_title: str, topic: str, style: str = "educational") -> Optional[Dict[str, Any]]:
"""Generate educational image for a lesson"""
try:
# Create varied educational prompts with different styles and approaches
import random
# Add variety with different visual styles and descriptors
visual_styles = [
"modern minimalist", "vibrant colorful", "clean professional", "sleek contemporary",
"bright engaging", "polished elegant", "dynamic visual", "crisp detailed"
]
art_styles = [
"digital illustration", "infographic design", "vector art", "educational diagram",
"technical illustration", "conceptual artwork", "instructional graphic", "learning visual"
]
descriptors = [
"high-quality", "detailed", "clear and informative", "visually appealing",
"educational focused", "learning oriented", "instructionally designed", "pedagogically sound"
]
# Randomly select style elements for variety
visual_style = random.choice(visual_styles)
art_style = random.choice(art_styles)
descriptor = random.choice(descriptors)
if style == "educational":
prompt = f"{descriptor.title()} {art_style} about {topic}. {visual_style} style with engaging visual elements."
elif style == "diagram":
prompt = f"Technical {art_style} showing {topic} concepts. {visual_style} design with clear visual hierarchy and {descriptor} presentation."
elif style == "concept":
prompt = f"Conceptual {art_style} of {topic}. {visual_style} visual representation with {descriptor} design elements."
else:
prompt = f"Professional {art_style} about {topic}. {visual_style}, {descriptor} educational style."
print(f"πŸŽ“ Generating educational image for: {lesson_title}")
# Use default parameters from config (already includes API credentials)
result = await self.generate_image_url(prompt)
if result:
result.update({
"lesson_title": lesson_title,
"topic": topic,
"style": style,
"educational": True
})
print(f"βœ… Educational image generated for: {lesson_title}")
return result
except Exception as e:
logger.error(f"Educational image generation failed: {e}")
print(f"❌ Educational image generation error: {e}")
return None
@staticmethod
def create_image_placeholder(lesson_title: str, description: str) -> str:
"""Create an image placeholder marker for lessons"""
return f"{{{{IMAGE_PLACEHOLDER:{lesson_title}:{description}}}}}"
def extract_image_placeholders(content: str) -> List[Dict[str, str]]:
"""Extract image placeholder markers from content"""
import re
pattern = r'\{\{IMAGE_PLACEHOLDER:([^:]+):([^}]+)\}\}'
matches = re.findall(pattern, content)
return [
{
"lesson_title": match[0],
"description": match[1],
"placeholder": f"{{{{IMAGE_PLACEHOLDER:{match[0]}:{match[1]}}}}}"
}
for match in matches
]
def replace_image_placeholders(content: str, images: List[Dict[str, Any]]) -> str:
"""Replace image placeholders with actual image HTML"""
print(f"πŸ”„ Replacing image placeholders in content ({len(images)} images available)")
if not images:
print("⚠️ No images provided for placeholder replacement")
return content
import re
# Track which images have been used to avoid duplication
used_images = set()
# Find all placeholders in content
placeholder_pattern = r'\{\{IMAGE_PLACEHOLDER:([^:]+):([^}]+)\}\}'
placeholders = re.findall(placeholder_pattern, content)
print(f"πŸ” Found {len(placeholders)} total placeholders in content")
for placeholder_lesson, placeholder_desc in placeholders:
print(f"πŸ“ Looking for image for placeholder: {placeholder_lesson} - {placeholder_desc[:50]}...")
# Find the best matching image that hasn't been used
best_match = None
for i, image in enumerate(images):
if i in used_images:
continue # Skip already used images
if not image or "data_url" not in image:
continue
image_lesson = image.get("lesson_title", "")
image_desc = image.get("placeholder_description", "")
# Check if this image matches the placeholder
if (image_lesson == placeholder_lesson and
(image_desc == placeholder_desc or
placeholder_desc in image_desc or
image_desc in placeholder_desc)):
best_match = (i, image)
break
if best_match:
image_index, image = best_match
used_images.add(image_index)
# Replace this specific placeholder
specific_placeholder = f"{{{{IMAGE_PLACEHOLDER:{placeholder_lesson}:{placeholder_desc}}}}}"
description = image.get("prompt", image.get("placeholder_description", "Educational image"))
img_html = f'<img src="{image["data_url"]}" alt="{description}" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); border: 2px solid #4a4a7a; margin: 1rem 0;">'
content = content.replace(specific_placeholder, img_html)
print(f"βœ… Replaced placeholder with image {image_index+1}: {placeholder_desc[:50]}...")
else:
print(f"⚠️ No matching image found for: {placeholder_lesson} - {placeholder_desc[:50]}...")
# Check if any placeholders remain
remaining_placeholders = re.findall(r'\{\{IMAGE_PLACEHOLDER:[^}]+\}\}', content)
if remaining_placeholders:
print(f"⚠️ {len(remaining_placeholders)} placeholders still remain unreplaced")
for placeholder in remaining_placeholders[:3]: # Show first 3
print(f" - {placeholder}")
else:
print("βœ… All image placeholders have been replaced")
print(f"πŸ“Š Used {len(used_images)} out of {len(images)} available images")
return content
def create_image_placeholder(lesson_title: str, description: str) -> str:
"""Convenience function for creating image placeholders"""
return ImageGenerator.create_image_placeholder(lesson_title, description)
# Global instance for convenience
_image_generator = ImageGenerator()
async def generate_educational_image(lesson_title: str, topic: str, style: str = "educational") -> Optional[Dict[str, Any]]:
"""Convenience function for generating educational images"""
return await _image_generator.generate_educational_image(lesson_title, topic, style)