BATUTO-ART commited on
Commit
5d76a18
·
verified ·
1 Parent(s): 9efa9ec

Upload image_tool_reve.py

Browse files
Files changed (1) hide show
  1. image_tool_reve.py +351 -0
image_tool_reve.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SISTEMA DE IMÁGENES REVE MEJORADO
3
+ - Generación por lotes
4
+ - Sello automático
5
+ - Optimización de parámetros
6
+ """
7
+ import os
8
+ import time
9
+ import base64
10
+ import json
11
+ import asyncio
12
+ from typing import List, Tuple, Optional, Dict, Any
13
+ from io import BytesIO
14
+ from pathlib import Path
15
+ from datetime import datetime
16
+
17
+ import aiohttp
18
+ import requests
19
+ from PIL import Image, ImageDraw, ImageFont
20
+ import concurrent.futures
21
+
22
+ # ===================== CONFIGURACIÓN =====================
23
+ REVE_API_KEY = os.getenv("REVE_API_KEY")
24
+ REVE_URL = "https://api.reve.com/v1/image/create"
25
+
26
+ # SOLUCIÓN: Verificación no bloqueante (Línea 29)
27
+ # En lugar de lanzar un error que detiene la app, definimos un estado.
28
+ REVE_AVAILABLE = REVE_API_KEY is not None and REVE_API_KEY != ""
29
+
30
+ # ===================== SELLO BATUTO AUTOMATIZADO =====================
31
+ class BatutoWatermark:
32
+ def __init__(self):
33
+ self.watermark_text = "BATUTO-ART"
34
+ self.default_font = None
35
+
36
+ def _get_font(self, image_width: int):
37
+ """Obtiene fuente apropiada para el tamaño de imagen"""
38
+ font_size = max(24, int(image_width * 0.035))
39
+
40
+ try:
41
+ # Intentar cargar fuentes comunes
42
+ font_paths = [
43
+ "arial.ttf",
44
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
45
+ "/System/Library/Fonts/Helvetica.ttc"
46
+ ]
47
+
48
+ for path in font_paths:
49
+ if os.path.exists(path):
50
+ return ImageFont.truetype(path, font_size)
51
+ except:
52
+ pass
53
+
54
+ # Fallback a fuente por defecto
55
+ return ImageFont.load_default()
56
+
57
+ def apply_watermark(self, image: Image.Image) -> Image.Image:
58
+ """Aplica sello BATUTO a la imagen"""
59
+ try:
60
+ img = image.convert("RGBA")
61
+ draw = ImageDraw.Draw(img)
62
+
63
+ font = self._get_font(img.width)
64
+
65
+ # Colores del sello (dorado con sombra)
66
+ color_primary = (212, 175, 55, 255)
67
+ color_shadow = (0, 0, 0, 180)
68
+
69
+ # Posición (esquina inferior derecha)
70
+ margin_x = int(img.width * 0.02)
71
+ margin_y = int(img.height * 0.02)
72
+ shadow_offset = 2
73
+
74
+ # Calcular tamaño del texto
75
+ text_bbox = draw.textbbox((0, 0), self.watermark_text, font=font)
76
+ text_width = text_bbox[2] - text_bbox[0]
77
+ text_height = text_bbox[3] - text_bbox[1]
78
+
79
+ # Posicionar en esquina inferior derecha
80
+ x = img.width - text_width - margin_x
81
+ y = img.height - text_height - margin_y
82
+
83
+ # Dibujar sombra
84
+ draw.text(
85
+ (x + shadow_offset, y + shadow_offset),
86
+ self.watermark_text,
87
+ font=font,
88
+ fill=color_shadow
89
+ )
90
+
91
+ # Dibujar texto principal
92
+ draw.text(
93
+ (x, y),
94
+ self.watermark_text,
95
+ font=font,
96
+ fill=color_primary
97
+ )
98
+
99
+ return img.convert("RGB")
100
+
101
+ except Exception as e:
102
+ print(f"⚠️ Error aplicando sello: {e}")
103
+ return image
104
+
105
+ # ===================== GENERADOR DE IMÁGENES ASÍNCRONO =====================
106
+ class AsyncReveGenerator:
107
+ def __init__(self, api_key: str, max_workers: int = 4):
108
+ if not api_key:
109
+ raise ValueError("API Key no proporcionada para AsyncReveGenerator")
110
+ self.api_key = api_key
111
+ self.max_workers = max_workers
112
+ self.watermarker = BatutoWatermark()
113
+ self.session = None
114
+
115
+ async def __aenter__(self):
116
+ self.session = aiohttp.ClientSession(
117
+ headers={
118
+ "Authorization": f"Bearer {self.api_key}",
119
+ "Content-Type": "application/json"
120
+ },
121
+ timeout=aiohttp.ClientTimeout(total=120)
122
+ )
123
+ return self
124
+
125
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
126
+ if self.session:
127
+ await self.session.close()
128
+
129
+ async def generate_single(
130
+ self,
131
+ prompt: str,
132
+ ratio: str = "1:1",
133
+ version: str = "latest",
134
+ index: int = 0
135
+ ) -> Optional[Tuple[Image.Image, Dict]]:
136
+ """Genera una sola imagen asíncronamente"""
137
+ try:
138
+ payload = {
139
+ "prompt": prompt,
140
+ "aspect_ratio": ratio,
141
+ "version": version
142
+ }
143
+
144
+ async with self.session.post(REVE_URL, json=payload) as response:
145
+ if response.status == 200:
146
+ data = await response.json()
147
+
148
+ if "image" in data:
149
+ # Decodificar imagen base64
150
+ img_data = base64.b64decode(data["image"])
151
+ img = Image.open(BytesIO(img_data)).convert("RGB")
152
+
153
+ # Aplicar sello
154
+ img = self.watermarker.apply_watermark(img)
155
+
156
+ metadata = {
157
+ "index": index,
158
+ "prompt": prompt,
159
+ "ratio": ratio,
160
+ "version": version,
161
+ "timestamp": datetime.now().isoformat(),
162
+ "api_response": data.get("id", "unknown")
163
+ }
164
+
165
+ return img, metadata
166
+ else:
167
+ print(f"❌ No image in API response for prompt {index}")
168
+ return None
169
+ else:
170
+ error_text = await response.text()
171
+ print(f"❌ API Error {response.status}: {error_text}")
172
+ return None
173
+
174
+ except Exception as e:
175
+ print(f"❌ Error generando imagen {index}: {e}")
176
+ return None
177
+
178
+ async def generate_batch(
179
+ self,
180
+ prompts: List[str],
181
+ ratio: str = "1:1",
182
+ version: str = "latest",
183
+ max_concurrent: int = None
184
+ ) -> List[Tuple[Image.Image, Dict]]:
185
+ """Genera múltiples imágenes concurrentemente"""
186
+ if not self.session:
187
+ raise RuntimeError("Session not initialized. Use async context manager.")
188
+
189
+ concurrent_limit = max_concurrent or self.max_workers
190
+ semaphore = asyncio.Semaphore(concurrent_limit)
191
+
192
+ async def limited_generate(prompt, idx):
193
+ async with semaphore:
194
+ return await self.generate_single(prompt, ratio, version, idx)
195
+
196
+ tasks = [limited_generate(prompt, i) for i, prompt in enumerate(prompts)]
197
+ results = await asyncio.gather(*tasks, return_exceptions=True)
198
+
199
+ # Filtrar resultados exitosos
200
+ successful = []
201
+ for result in results:
202
+ if isinstance(result, tuple) and result[0] is not None:
203
+ successful.append(result)
204
+ elif isinstance(result, Exception):
205
+ print(f"⚠️ Exception in generation: {result}")
206
+
207
+ return successful
208
+
209
+ # ===================== FUNCIONES PÚBLICAS =====================
210
+ def generar_imagen_reve(
211
+ prompt: str,
212
+ ratio: str = "1:1",
213
+ version: str = "latest",
214
+ apply_watermark: bool = True,
215
+ save_path: Optional[str] = None
216
+ ) -> Image.Image:
217
+ """
218
+ Genera una imagen usando REVE API (versión síncrona)
219
+ """
220
+ # VERIFICACIÓN SEGURA: La app no se detiene aquí
221
+ if not REVE_AVAILABLE:
222
+ raise RuntimeError("""
223
+ 🔧 Configuración de REVE API Requerida
224
+
225
+ Para usar la generación de imágenes, configura tu clave:
226
+ 1. Ve a 'Settings' en tu Space de Hugging Face.
227
+ 2. Haz clic en 'Repository secrets'.
228
+ 3. Agrega: REVE_API_KEY = tu_clave_secreta
229
+ 4. Guarda y reinicia el Space.
230
+
231
+ La aplicación seguirá funcionando para otras funcionalidades.
232
+ """)
233
+
234
+ payload = {
235
+ "prompt": prompt,
236
+ "aspect_ratio": ratio,
237
+ "version": version
238
+ }
239
+
240
+ headers = {
241
+ "Authorization": f"Bearer {REVE_API_KEY}",
242
+ "Content-Type": "application/json"
243
+ }
244
+
245
+ try:
246
+ response = requests.post(
247
+ REVE_URL,
248
+ headers=headers,
249
+ json=payload,
250
+ timeout=120
251
+ )
252
+
253
+ if response.status_code != 200:
254
+ raise RuntimeError(f"❌ Error REVE API: {response.status_code} - {response.text}")
255
+
256
+ data = response.json()
257
+
258
+ if "image" not in data:
259
+ raise RuntimeError("❌ No se encontró imagen en la respuesta de REVE")
260
+
261
+ # Decodificar y cargar imagen
262
+ img_bytes = base64.b64decode(data["image"])
263
+ img = Image.open(BytesIO(img_bytes)).convert("RGB")
264
+
265
+ # Aplicar sello si está habilitado
266
+ if apply_watermark:
267
+ watermarker = BatutoWatermark()
268
+ img = watermarker.apply_watermark(img)
269
+
270
+ # Guardar si se especifica ruta
271
+ if save_path:
272
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
273
+ img.save(save_path)
274
+ print(f"✅ Imagen guardada en: {save_path}")
275
+
276
+ return img
277
+
278
+ except requests.exceptions.Timeout:
279
+ raise RuntimeError("⏰ Timeout de REVE API (120s)")
280
+ except Exception as e:
281
+ raise RuntimeError(f"❌ Error generando imagen: {str(e)}")
282
+
283
+ async def generar_imagenes_concurrentes(
284
+ prompts: List[str],
285
+ ratio: str = "1:1",
286
+ version: str = "latest",
287
+ max_concurrent: int = 4
288
+ ) -> List[Image.Image]:
289
+ """
290
+ Genera múltiples imágenes concurrentemente
291
+ """
292
+ if not REVE_AVAILABLE:
293
+ raise RuntimeError("REVE_API_KEY no configurada. Verifica los Secretos del Space.")
294
+
295
+ async with AsyncReveGenerator(REVE_API_KEY, max_concurrent) as generator:
296
+ results = await generator.generate_batch(prompts, ratio, version, max_concurrent)
297
+ return [img for img, _ in results]
298
+
299
+ def generar_imagen_con_metadatos(
300
+ prompt: str,
301
+ ratio: str = "1:1",
302
+ version: str = "latest"
303
+ ) -> Dict[str, Any]:
304
+ """
305
+ Genera imagen con metadatos completos
306
+ """
307
+ start_time = time.time()
308
+
309
+ # Verificación consistente
310
+ if not REVE_AVAILABLE:
311
+ return {
312
+ "image": None,
313
+ "metadata": {
314
+ "success": False,
315
+ "error": "REVE_API_KEY no configurada en los Secretos del Space.",
316
+ "generation_time": 0.0,
317
+ "timestamp": datetime.now().isoformat()
318
+ }
319
+ }
320
+
321
+ try:
322
+ img = generar_imagen_reve(prompt, ratio, version, apply_watermark=True)
323
+
324
+ # Crear metadatos
325
+ metadata = {
326
+ "success": True,
327
+ "prompt": prompt,
328
+ "aspect_ratio": ratio,
329
+ "version": version,
330
+ "generation_time": time.time() - start_time,
331
+ "image_size": img.size,
332
+ "image_mode": img.mode,
333
+ "timestamp": datetime.now().isoformat(),
334
+ "watermark_applied": True
335
+ }
336
+
337
+ return {
338
+ "image": img,
339
+ "metadata": metadata
340
+ }
341
+
342
+ except Exception as e:
343
+ return {
344
+ "image": None,
345
+ "metadata": {
346
+ "success": False,
347
+ "error": str(e),
348
+ "generation_time": time.time() - start_time,
349
+ "timestamp": datetime.now().isoformat()
350
+ }
351
+ }