### Por qué GPT Image 2
GPT Image 2 genera imágenes fotorrealistas de altísima calidad y, crucialmente, soporta image-edit para mantener continuidad facial entre slides. Sin image-edit, el Slide 2 parecería una persona diferente al Slide 1, lo cual rompe la ilusión del "antes y después" y destruye la narrativa.
### Flujo de renderizado por carrusel
Para cada uno de los 5 carruseles:
- Slide 1 →
gpt-image-2 con el prompt renderizado del Template A.
- Slide 2 →
gpt-image-2 con image-edit, usando la imagen del Slide 1 como base. Esto mantiene la misma estructura facial pero con piel limpia.
- Slide 3 →
gpt-image-2 con el prompt renderizado del Template C (lockscreen, no necesita continuidad facial).
### Código del pipeline
`python
import asyncio
from openai import AsyncOpenAI
import base64
from pathlib import Path
from templates import PromptTemplates, CarouselVariables
client = AsyncOpenAI()
async def render_slide1(vars: CarouselVariables, output_dir: Path) -> Path:
"""Renderiza Slide 1: imagen base del 'antes'."""
prompt = PromptTemplates.render_slide1(vars)
response = await client.images.generate(
model="gpt-image-2",
prompt=prompt,
size="1080x1920", # 9:16 vertical
quality="hd",
n=1,
)
# Guardar imagen
image_path = output_dir / "slide1.png"
# response.data[0].url para descargar, o response.data[0].b64_json
# ... lógica de descarga ...
return image_path
async def render_slide2(vars: CarouselVariables, slide1_path: Path, output_dir: Path) -> Path:
"""Renderiza Slide 2 usando image-edit sobre Slide 1 para continuidad facial."""
prompt = PromptTemplates.render_slide2(vars)
with open(slide1_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
response = await client.images.edit(
model="gpt-image-2",
image=image_data, # Imagen base para mantener identidad
prompt=prompt, # Instrucciones de transformación
size="1080x1920",
n=1,
)
image_path = output_dir / "slide2.png"
# ... lógica de descarga ...
return image_path
async def render_slide3(vars: CarouselVariables, output_dir: Path) -> Path:
"""Renderiza Slide 3: lockscreen con CTA."""
prompt = PromptTemplates.render_slide3(vars)
response = await client.images.generate(
model="gpt-image-2",
prompt=prompt,
size="1080x1920",
quality="hd",
n=1,
)
image_path = output_dir / "slide3.png"
# ... lógica de descarga ...
return image_path
async def render_carousel(vars: CarouselVariables, carousel_id: int, base_dir: Path) -> dict:
"""Renderiza un carrusel completo de 3 slides."""
output_dir = base_dir / f"carousel_{carousel_id:02d}"
output_dir.mkdir(parents=True, exist_ok=True)
# Slide 1 primero
slide1 = await render_slide1(vars, output_dir)
# Slide 2 y 3 se pueden paralelizar
slide2, slide3 = await asyncio.gather(
render_slide2(vars, slide1, output_dir),
render_slide3(vars, output_dir),
)
return {
"id": carousel_id,
"variables": vars,
"slides": [slide1, slide2, slide3],
}
`
### Paralelización: 15 imágenes simultáneas
La verdadera potencia está en disparar los 5 carruseles completos en paralelo:
`python
async def render_batch(variables: list[CarouselVariables], base_dir: Path) -> list[dict]:
"""Renderiza 5 carruseles = 15 imágenes en paralelo."""
tasks = [
render_carousel(vars, i, base_dir)
for i, vars in enumerate(variables)
]
results = await asyncio.gather(*tasks)
return results
`
Esto ejecuta los 5 carruseles simultáneamente. Dentro de cada carrusel, el Slide 1 es secuencial (porque el Slide 2 depende de él vía image-edit), pero los Slides 2 y 3 se disparan en paralelo una vez que el Slide 1 está listo. El resultado: 15 imágenes generadas en el tiempo que toma generar ~2 imágenes secuenciales.