import openai import time import logging from tenacity import retry, wait_exponential, stop_after_attempt from PIL import Image import requests from io import BytesIO import yaml class FrameGenerator: def __init__(self, api_key, config_path='configs/frame_templates.yaml'): openai.api_key = api_key self.logger = logging.getLogger(__name__) self.templates = self._load_templates(config_path) self.rate_limit = 5 # Llamadas por minuto self.last_call = 0 def _load_templates(self, config_path): try: with open(config_path) as f: return yaml.safe_load(f)['styles'] except Exception as e: self.logger.error(f"Error loading templates: {str(e)}") return {} @retry( wait=wait_exponential(multiplier=1, min=4, max=60), stop=stop_after_attempt(3), reraise=True ) def generate_frame(self, image_url, metadata): self._throttle_requests() style = metadata.get('style', 'minimalista') template = self.templates.get(style, self.templates['minimalista']) prompt = self._build_prompt(template, metadata) try: response = openai.images.generate( model="dall-e-3", prompt=prompt, size="1024x1024", quality="hd", n=1, response_format="url" ) return self._composite_frame( image_url, response.data[0].url, template['mask_size'] ) except openai.RateLimitError: self.logger.warning("Rate limit exceeded, retrying...") time.sleep(60) raise except Exception as e: self.logger.error(f"Generation failed: {str(e)}") return None def _build_prompt(self, template, metadata): color_str = ", ".join(metadata['colors'][:3]) return ( f"High-quality frame for {metadata['style']} painting, " f"main colors: {color_str}. {template['prompt']} " "No text, no signatures, pure decorative frame." ) def _throttle_requests(self): elapsed = time.time() - self.last_call if elapsed < 60 / self.rate_limit: time.sleep(60 / self.rate_limit - elapsed) self.last_call = time.time() def _composite_frame(self, original_url, frame_url, mask_size=800): try: original = Image.open(requests.get(original_url, stream=True).raw) frame = Image.open(requests.get(frame_url, stream=True).raw) original = original.resize((mask_size, mask_size)) position = ((frame.width - original.width) // 2, (frame.height - original.height) // 2) composite = frame.copy() composite.paste(original, position) return composite except Exception as e: self.logger.error(f"Compositing failed: {str(e)}") return None