- Estructura completa del proyecto con FastAPI - Modelos de base de datos (productos, servicios, posts, calendario, interacciones) - Publishers para X, Threads, Instagram, Facebook - Generador de contenido con DeepSeek API - Worker de Celery con tareas programadas - Dashboard básico con templates HTML - Docker Compose para despliegue - Documentación completa Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
175 lines
5.0 KiB
Python
175 lines
5.0 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 = Html2Image(
|
|
output_path=str(self.output_dir),
|
|
custom_flags=['--no-sandbox', '--disable-gpu']
|
|
)
|
|
|
|
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()
|