BATUTO-ART commited on
Commit
5ca081e
·
verified ·
1 Parent(s): 7df39e3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +271 -0
app.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import gradio as gr
4
+ from PIL import Image
5
+ from io import BytesIO
6
+ import tempfile
7
+ import uuid
8
+ import time
9
+
10
+ # ======================================================
11
+ # CONFIGURACIÓN BATUTO-ART FINAL APOCALYPSE
12
+ # ======================================================
13
+ APP_NAME = "BATUTO-ART_Final Apocalypse"
14
+ API_URL = "https://api.reve.com/v1/image/create"
15
+
16
+ SUPPORTED_ASPECT_RATIOS = ["1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16"]
17
+ SUPPORTED_VERSIONS = ["latest", "reve-create@20250915"]
18
+ SUPPORTED_FORMATS = {
19
+ "PNG": "image/png",
20
+ "JPEG": "image/jpeg",
21
+ "WEBP": "image/webp"
22
+ }
23
+
24
+ # ======================================================
25
+ # MOTOR DE PROMPT APOCALYPSE SENSUAL (+18, NO EXPLÍCITO)
26
+ # ======================================================
27
+ def build_apocalypse_sensual_prompt(name: str):
28
+ import random
29
+
30
+ roles = [
31
+ "luxury lingerie model",
32
+ "high-fashion editorial muse",
33
+ "sensual portrait subject",
34
+ "glamour editorial model"
35
+ ]
36
+
37
+ hair = [
38
+ "long silky black hair",
39
+ "soft wavy brunette hair",
40
+ "platinum blonde hair with natural volume",
41
+ "deep auburn hair flowing softly"
42
+ ]
43
+
44
+ eyes = [
45
+ "intense green eyes with a captivating gaze",
46
+ "deep brown eyes with a seductive expression",
47
+ "light hazel eyes with soft intensity",
48
+ "piercing blue eyes"
49
+ ]
50
+
51
+ outfits = [
52
+ "elegant lace lingerie",
53
+ "silk robe slightly open",
54
+ "form-fitting satin dress",
55
+ "luxury bodysuit with delicate details"
56
+ ]
57
+
58
+ poses = [
59
+ "reclining gracefully on a velvet sofa",
60
+ "standing confidently with sensual posture",
61
+ "sitting with crossed legs and relaxed shoulders",
62
+ "leaning slightly forward with a soft expression"
63
+ ]
64
+
65
+ environments = [
66
+ "luxury hotel suite with warm ambient lighting",
67
+ "minimalist studio with deep shadows",
68
+ "private penthouse bedroom with cinematic light",
69
+ "high-end editorial studio setting"
70
+ ]
71
+
72
+ return (
73
+ f"Ultra realistic sensual editorial photography of an adult woman named {name}, "
74
+ f"a {random.choice(roles)}, "
75
+ f"with {random.choice(hair)}, {random.choice(eyes)}, "
76
+ f"{random.choice(poses)}, wearing {random.choice(outfits)}, "
77
+ f"in a {random.choice(environments)}. "
78
+ f"Highly detailed skin texture, natural curves, realistic anatomy, "
79
+ f"soft cinematic lighting, subtle shadows, shallow depth of field, "
80
+ f"85mm lens, f1.8, high-end fashion photography, "
81
+ f"tasteful, elegant, seductive mood, "
82
+ f"no nudity, no explicit sexual acts, no porn, no illustration."
83
+ )
84
+
85
+ # ======================================================
86
+ # GENERADOR DE IMÁGENES UNIFICADO
87
+ # ======================================================
88
+ def generate_images(
89
+ api_key,
90
+ mode,
91
+ name,
92
+ prompt_manual,
93
+ aspect_ratio,
94
+ version,
95
+ image_format,
96
+ quantity
97
+ ):
98
+ # Validación API Key
99
+ if not api_key:
100
+ return [], "❌ Falta la clave API."
101
+
102
+ # Construcción / validación del prompt
103
+ if mode == "APOCALYPSE SENSUAL":
104
+ if not name or not name.strip():
105
+ return [], "❌ Ingresa un nombre para el personaje (+18)."
106
+ prompt = build_apocalypse_sensual_prompt(name.strip())
107
+ else:
108
+ if not prompt_manual or not prompt_manual.strip():
109
+ return [], "❌ El prompt manual está vacío."
110
+ if len(prompt_manual) > 2560:
111
+ return [], "❌ El prompt manual supera el máximo de 2560 caracteres."
112
+ prompt = prompt_manual.strip()
113
+
114
+ headers = {
115
+ "Authorization": f"Bearer {api_key}",
116
+ "Accept": SUPPORTED_FORMATS[image_format],
117
+ "Content-Type": "application/json"
118
+ }
119
+
120
+ images = []
121
+ logs = []
122
+
123
+ for i in range(quantity):
124
+ payload = {
125
+ "prompt": prompt,
126
+ "aspect_ratio": aspect_ratio,
127
+ "version": version
128
+ }
129
+
130
+ try:
131
+ response = requests.post(
132
+ API_URL,
133
+ headers=headers,
134
+ json=payload,
135
+ timeout=90
136
+ )
137
+
138
+ if response.status_code == 402:
139
+ return images, "❌ Créditos insuficientes en tu cuenta."
140
+
141
+ if response.status_code != 200:
142
+ return images, f"❌ Error {response.status_code}: {response.text}"
143
+
144
+ # Violación de contenido según cabecera de la API
145
+ if response.headers.get("X-Reve-Content-Violation") == "true":
146
+ return images, "⚠️ El prompt activó una violación de contenido."
147
+
148
+ image = Image.open(BytesIO(response.content))
149
+ images.append(image)
150
+
151
+ logs.append(
152
+ f"✔ Imagen {i+1} | "
153
+ f"Créditos usados: {response.headers.get('X-Reve-Credits-Used')} | "
154
+ f"Restantes: {response.headers.get('X-Reve-Credits-Remaining')}"
155
+ )
156
+
157
+ time.sleep(0.3)
158
+
159
+ except Exception as e:
160
+ return images, f"❌ Error inesperado: {str(e)}"
161
+
162
+ return images, "\n".join(logs)
163
+
164
+ # ======================================================
165
+ # DESCARGA
166
+ # ======================================================
167
+ def save_images(images):
168
+ files = []
169
+ for img in images:
170
+ name = f"BATUTO_ART_FINAL_{uuid.uuid4().hex[:8]}.png"
171
+ path = os.path.join(tempfile.gettempdir(), name)
172
+ img.save(path)
173
+ files.append(path)
174
+ return files
175
+
176
+ # ======================================================
177
+ # UI GRADIO FINAL APOCALYPSE
178
+ # ======================================================
179
+ with gr.Blocks(title=APP_NAME, theme=gr.themes.Soft()) as demo:
180
+
181
+ gr.Markdown(f"# 🔥 {APP_NAME}")
182
+ gr.Markdown(
183
+ "Generación profesional de imágenes con **Reve Create API**, "
184
+ "incluye modo adulto +18 (sensual, NO explícito) y modo manual avanzado."
185
+ )
186
+
187
+ api_key = gr.Textbox(
188
+ label="API Key Reve",
189
+ type="password",
190
+ value=os.getenv("REVE_API_KEY", ""),
191
+ placeholder="Bearer token..."
192
+ )
193
+
194
+ mode = gr.Radio(
195
+ ["APOCALYPSE SENSUAL (+18)", "Manual"],
196
+ value="APOCALYPSE SENSUAL (+18)",
197
+ label="Modo"
198
+ )
199
+
200
+ name = gr.Textbox(
201
+ label="Nombre del personaje (+18, solo modo Apocalypse)",
202
+ placeholder="Ej. Valeria, Sofía, Isabella..."
203
+ )
204
+
205
+ prompt_manual = gr.Textbox(
206
+ label="Prompt Manual (modo Manual)",
207
+ lines=5,
208
+ max_length=2560,
209
+ placeholder="Describe la imagen con máximo detalle..."
210
+ )
211
+
212
+ with gr.Row():
213
+ aspect = gr.Dropdown(
214
+ SUPPORTED_ASPECT_RATIOS,
215
+ value="3:2",
216
+ label="Aspect Ratio"
217
+ )
218
+ version = gr.Dropdown(
219
+ SUPPORTED_VERSIONS,
220
+ value="latest",
221
+ label="Modelo"
222
+ )
223
+
224
+ with gr.Row():
225
+ image_format = gr.Dropdown(
226
+ list(SUPPORTED_FORMATS.keys()),
227
+ value="PNG",
228
+ label="Formato"
229
+ )
230
+ quantity = gr.Slider(
231
+ 1, 8,
232
+ step=1,
233
+ value=1,
234
+ label="Cantidad"
235
+ )
236
+
237
+ generate = gr.Button("🪄 GENERAR APOCALYPSE ART", variant="primary")
238
+
239
+ status = gr.Textbox(label="Estado", lines=6)
240
+ gallery = gr.Gallery(label="Resultados", columns=4, height=520)
241
+
242
+ download = gr.Button("⬇️ Descargar Todo")
243
+ files = gr.File(visible=False)
244
+
245
+ generate.click(
246
+ fn=generate_images,
247
+ inputs=[
248
+ api_key,
249
+ mode,
250
+ name,
251
+ prompt_manual,
252
+ aspect,
253
+ version,
254
+ image_format,
255
+ quantity
256
+ ],
257
+ outputs=[gallery, status]
258
+ )
259
+
260
+ download.click(
261
+ fn=save_images,
262
+ inputs=[gallery],
263
+ outputs=[files]
264
+ )
265
+
266
+ # ======================================================
267
+ # RUN
268
+ # ======================================================
269
+ if __name__ == "__main__":
270
+ demo.launch(server_name="0.0.0.0", server_port=7860)
271
+