Files
social-media-automation/app/services/image_generator.py
Consultoría AS 541a8484a7 feat(phase-1): Complete foundation setup
- Add User model and authentication system with JWT cookies
- Implement login/logout routes and protected dashboard
- Add Alembic database migration configuration
- Add create_admin.py script for initial user setup
- Make ContentGenerator and ImageGenerator lazy-initialized
- Add comprehensive API keys setup documentation
- Fix startup errors when services unavailable

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 01:30:15 +00:00

187 lines
5.4 KiB
Python

"""
Servicio de generación de imágenes para posts.
"""
import os
from typing import Dict, Optional
from pathlib import Path
from html2image import Html2Image
from PIL import Image
from jinja2 import Environment, FileSystemLoader
from app.core.config import settings
class ImageGenerator:
"""Generador de imágenes usando plantillas HTML."""
def __init__(self):
self.templates_dir = Path("templates")
self.output_dir = Path("uploads/generated")
self.output_dir.mkdir(parents=True, exist_ok=True)
self.jinja_env = Environment(
loader=FileSystemLoader(self.templates_dir)
)
self._hti = None
@property
def hti(self):
"""Lazy initialization de Html2Image."""
if self._hti is None:
try:
self._hti = Html2Image(
output_path=str(self.output_dir),
custom_flags=['--no-sandbox', '--disable-gpu']
)
except FileNotFoundError:
raise RuntimeError(
"Chrome/Chromium no encontrado. Instala Chrome o ejecuta en Docker."
)
return self._hti
def _render_template(self, template_name: str, variables: Dict) -> str:
"""Renderizar una plantilla HTML con variables."""
template = self.jinja_env.get_template(template_name)
return template.render(**variables)
async def generate_tip_card(
self,
title: str,
content: str,
category: str,
output_name: str
) -> str:
"""Generar imagen de tip tech."""
variables = {
"title": title,
"content": content,
"category": category,
"logo_url": f"{settings.BUSINESS_WEBSITE}/logo.png",
"website": settings.BUSINESS_WEBSITE,
"business_name": settings.BUSINESS_NAME
}
html_content = self._render_template("tip_card.html", variables)
# Generar imagen
output_file = f"{output_name}.png"
self.hti.screenshot(
html_str=html_content,
save_as=output_file,
size=(1080, 1080)
)
return str(self.output_dir / output_file)
async def generate_product_card(
self,
name: str,
price: float,
image_url: str,
highlights: list,
output_name: str
) -> str:
"""Generar imagen de producto."""
variables = {
"name": name,
"price": f"${price:,.2f} MXN",
"image_url": image_url,
"highlights": highlights[:3], # Máximo 3 highlights
"logo_url": f"{settings.BUSINESS_WEBSITE}/logo.png",
"website": settings.BUSINESS_WEBSITE,
"business_name": settings.BUSINESS_NAME
}
html_content = self._render_template("product_card.html", variables)
output_file = f"{output_name}.png"
self.hti.screenshot(
html_str=html_content,
save_as=output_file,
size=(1080, 1080)
)
return str(self.output_dir / output_file)
async def generate_service_card(
self,
name: str,
tagline: str,
benefits: list,
icon: str,
output_name: str
) -> str:
"""Generar imagen de servicio."""
variables = {
"name": name,
"tagline": tagline,
"benefits": benefits[:4], # Máximo 4 beneficios
"icon": icon,
"logo_url": f"{settings.BUSINESS_WEBSITE}/logo.png",
"website": settings.BUSINESS_WEBSITE,
"business_name": settings.BUSINESS_NAME
}
html_content = self._render_template("service_card.html", variables)
output_file = f"{output_name}.png"
self.hti.screenshot(
html_str=html_content,
save_as=output_file,
size=(1080, 1080)
)
return str(self.output_dir / output_file)
async def resize_for_platform(
self,
image_path: str,
platform: str
) -> str:
"""Redimensionar imagen para una plataforma específica."""
sizes = {
"x": (1200, 675), # 16:9
"threads": (1080, 1080), # 1:1
"instagram": (1080, 1080), # 1:1
"facebook": (1200, 630) # ~1.9:1
}
target_size = sizes.get(platform, (1080, 1080))
img = Image.open(image_path)
# Crear nueva imagen con el tamaño objetivo
new_img = Image.new('RGB', target_size, (26, 26, 46)) # Color de fondo de la marca
# Calcular posición para centrar
img_ratio = img.width / img.height
target_ratio = target_size[0] / target_size[1]
if img_ratio > target_ratio:
# Imagen más ancha
new_width = target_size[0]
new_height = int(new_width / img_ratio)
else:
# Imagen más alta
new_height = target_size[1]
new_width = int(new_height * img_ratio)
img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Centrar en la nueva imagen
x = (target_size[0] - new_width) // 2
y = (target_size[1] - new_height) // 2
new_img.paste(img_resized, (x, y))
# Guardar
output_path = image_path.replace('.png', f'_{platform}.png')
new_img.save(output_path, 'PNG', quality=95)
return output_path
# Instancia global
image_generator = ImageGenerator()