Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -17,31 +17,33 @@ logger = logging.getLogger(__name__)
|
|
| 17 |
# Clave API de Pexels (configurar en Secrets de Hugging Face)
|
| 18 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY", "YOUR_API_KEY")
|
| 19 |
|
| 20 |
-
# --- Funciones optimizadas
|
| 21 |
|
| 22 |
def extract_keywords(text, max_keywords=3):
|
| 23 |
-
"""Extrae palabras clave usando un método
|
| 24 |
-
# Limpieza de texto
|
| 25 |
text = re.sub(r'[^\w\s]', '', text.lower())
|
| 26 |
-
words =
|
| 27 |
|
| 28 |
-
# Palabras comunes a excluir
|
| 29 |
-
stop_words = {
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
# Frecuencia de palabras
|
| 32 |
word_freq = {}
|
| 33 |
for word in words:
|
| 34 |
if len(word) > 3 and word not in stop_words:
|
| 35 |
word_freq[word] = word_freq.get(word, 0) + 1
|
| 36 |
|
| 37 |
-
# Ordenar por frecuencia
|
| 38 |
-
sorted_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)
|
| 39 |
return [word for word, _ in sorted_words[:max_keywords]]
|
| 40 |
|
| 41 |
def search_pexels_videos(keywords, per_query=2):
|
| 42 |
-
"""Busca videos en Pexels
|
| 43 |
-
if not PEXELS_API_KEY:
|
| 44 |
-
logger.error("API_KEY de Pexels no configurada")
|
| 45 |
return []
|
| 46 |
|
| 47 |
headers = {"Authorization": PEXELS_API_KEY}
|
|
@@ -49,6 +51,7 @@ def search_pexels_videos(keywords, per_query=2):
|
|
| 49 |
|
| 50 |
for query in keywords:
|
| 51 |
try:
|
|
|
|
| 52 |
params = {
|
| 53 |
"query": query,
|
| 54 |
"per_page": per_query,
|
|
@@ -60,7 +63,7 @@ def search_pexels_videos(keywords, per_query=2):
|
|
| 60 |
"https://api.pexels.com/videos/search",
|
| 61 |
headers=headers,
|
| 62 |
params=params,
|
| 63 |
-
timeout=
|
| 64 |
)
|
| 65 |
|
| 66 |
if response.status_code == 200:
|
|
@@ -76,190 +79,213 @@ def search_pexels_videos(keywords, per_query=2):
|
|
| 76 |
key=lambda x: x.get("width", 0) * x.get("height", 0)
|
| 77 |
)
|
| 78 |
video_urls.append(best_quality["link"])
|
|
|
|
|
|
|
|
|
|
| 79 |
except Exception as e:
|
| 80 |
-
logger.error(f"Error buscando videos: {e}")
|
| 81 |
|
| 82 |
return video_urls
|
| 83 |
|
| 84 |
async def generate_tts(text, output_path, voice="es-ES-ElviraNeural"):
|
| 85 |
-
"""Genera audio TTS
|
| 86 |
try:
|
| 87 |
communicate = edge_tts.Communicate(text, voice)
|
| 88 |
await communicate.save(output_path)
|
|
|
|
| 89 |
return True
|
| 90 |
except Exception as e:
|
| 91 |
-
logger.error(f"Error en TTS: {e}")
|
| 92 |
return False
|
| 93 |
|
| 94 |
def download_video(url, temp_dir):
|
| 95 |
-
"""Descarga
|
| 96 |
try:
|
| 97 |
-
|
|
|
|
| 98 |
response.raise_for_status()
|
| 99 |
|
| 100 |
-
filename = f"video_{os.getpid()}.mp4"
|
| 101 |
filepath = os.path.join(temp_dir, filename)
|
| 102 |
|
| 103 |
with open(filepath, 'wb') as f:
|
| 104 |
for chunk in response.iter_content(chunk_size=8192):
|
| 105 |
f.write(chunk)
|
| 106 |
-
|
|
|
|
| 107 |
return filepath
|
| 108 |
except Exception as e:
|
| 109 |
-
logger.error(f"Error descargando video: {e}")
|
| 110 |
return None
|
| 111 |
|
| 112 |
def create_video(audio_path, video_paths, output_path):
|
| 113 |
-
"""Crea el video final
|
| 114 |
try:
|
| 115 |
-
# Crear archivo de lista para concatenación
|
| 116 |
-
|
| 117 |
-
with open(
|
| 118 |
for path in video_paths:
|
| 119 |
f.write(f"file '{os.path.basename(path)}'\n")
|
| 120 |
|
| 121 |
-
#
|
| 122 |
-
os.chdir(os.path.dirname(video_paths[0]))
|
| 123 |
-
|
| 124 |
-
# Comando FFmpeg para concatenar videos y añadir audio
|
| 125 |
cmd = [
|
| 126 |
"ffmpeg", "-y",
|
| 127 |
"-f", "concat",
|
| 128 |
"-safe", "0",
|
| 129 |
-
"-i",
|
| 130 |
"-i", audio_path,
|
| 131 |
-
"-c:v", "
|
|
|
|
|
|
|
| 132 |
"-c:a", "aac",
|
|
|
|
| 133 |
"-shortest",
|
|
|
|
| 134 |
output_path
|
| 135 |
]
|
| 136 |
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
return True
|
| 139 |
except Exception as e:
|
| 140 |
-
logger.error(f"Error creando video: {e}")
|
| 141 |
return False
|
| 142 |
finally:
|
| 143 |
-
|
| 144 |
-
os.
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
try:
|
| 149 |
-
speech = AudioSegment.from_file(audio_path)
|
| 150 |
-
background = AudioSegment.from_file(music_path) - 20 # Reducir volumen
|
| 151 |
-
|
| 152 |
-
# Extender música si es necesario
|
| 153 |
-
if len(background) < len(speech):
|
| 154 |
-
loops = math.ceil(len(speech) / len(background))
|
| 155 |
-
background = background * loops
|
| 156 |
-
|
| 157 |
-
combined = speech.overlay(background[:len(speech)])
|
| 158 |
-
|
| 159 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
|
| 160 |
-
combined.export(tmp_file.name, format="mp3")
|
| 161 |
-
return tmp_file.name
|
| 162 |
-
except Exception as e:
|
| 163 |
-
logger.error(f"Error mezclando audio: {e}")
|
| 164 |
-
return audio_path
|
| 165 |
|
| 166 |
async def generate_video(text, music_file=None):
|
| 167 |
-
"""Función principal
|
| 168 |
temp_dir = tempfile.mkdtemp()
|
| 169 |
-
|
| 170 |
|
| 171 |
try:
|
| 172 |
# 1. Generar audio TTS
|
| 173 |
tts_path = os.path.join(temp_dir, "audio.mp3")
|
| 174 |
if not await generate_tts(text, tts_path):
|
| 175 |
-
return None, "Error generando voz"
|
| 176 |
-
output_files.append(tts_path)
|
| 177 |
-
|
| 178 |
-
# 2. Añadir música de fondo si está disponible
|
| 179 |
-
final_audio = tts_path
|
| 180 |
-
if music_file:
|
| 181 |
-
mixed_audio = add_background_music(tts_path, music_file)
|
| 182 |
-
if mixed_audio != tts_path:
|
| 183 |
-
final_audio = mixed_audio
|
| 184 |
-
output_files.append(mixed_audio)
|
| 185 |
|
| 186 |
-
#
|
| 187 |
keywords = extract_keywords(text)
|
| 188 |
-
logger.info(f"Palabras clave identificadas: {keywords}")
|
| 189 |
-
|
| 190 |
if not keywords:
|
| 191 |
-
return None, "No se pudieron extraer palabras clave del texto"
|
|
|
|
| 192 |
|
| 193 |
-
#
|
| 194 |
video_urls = search_pexels_videos(keywords)
|
| 195 |
if not video_urls:
|
| 196 |
-
return None, "No se encontraron videos para las palabras clave"
|
| 197 |
|
| 198 |
video_paths = []
|
| 199 |
for url in video_urls:
|
| 200 |
path = download_video(url, temp_dir)
|
| 201 |
if path:
|
| 202 |
video_paths.append(path)
|
| 203 |
-
output_files.append(path)
|
| 204 |
|
| 205 |
if not video_paths:
|
| 206 |
-
return None, "Error descargando videos"
|
| 207 |
|
| 208 |
-
#
|
| 209 |
output_path = os.path.join(temp_dir, "final_video.mp4")
|
| 210 |
-
if create_video(
|
| 211 |
-
return
|
| 212 |
-
|
| 213 |
-
|
| 214 |
|
| 215 |
except Exception as e:
|
| 216 |
logger.exception("Error inesperado")
|
| 217 |
-
return None, f"Error: {str(e)}"
|
| 218 |
finally:
|
| 219 |
-
#
|
| 220 |
pass
|
| 221 |
|
| 222 |
-
# --- Interfaz de Gradio
|
| 223 |
-
with gr.Blocks(title="Generador Automático de Videos
|
| 224 |
-
gr.Markdown("
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
|
| 227 |
with gr.Row():
|
| 228 |
-
with gr.Column():
|
| 229 |
text_input = gr.Textbox(
|
| 230 |
label="Texto para el video",
|
| 231 |
-
placeholder="
|
| 232 |
-
lines=5
|
|
|
|
| 233 |
)
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
-
with gr.Column():
|
| 241 |
-
video_output = gr.Video(
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
generate_btn.click(
|
| 245 |
-
fn=lambda: (None, "Procesando...
|
| 246 |
outputs=[video_output, status_output],
|
| 247 |
queue=False
|
| 248 |
).then(
|
| 249 |
fn=generate_video,
|
| 250 |
-
inputs=[text_input
|
| 251 |
outputs=[video_output, status_output]
|
| 252 |
)
|
| 253 |
|
| 254 |
-
gr.Markdown("###
|
| 255 |
gr.Markdown("""
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
- **Procesamiento eficiente** con FFmpeg
|
| 261 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
|
| 263 |
# Para Hugging Face Spaces
|
| 264 |
if __name__ == "__main__":
|
| 265 |
-
demo.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# Clave API de Pexels (configurar en Secrets de Hugging Face)
|
| 18 |
PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY", "YOUR_API_KEY")
|
| 19 |
|
| 20 |
+
# --- Funciones optimizadas y corregidas ---
|
| 21 |
|
| 22 |
def extract_keywords(text, max_keywords=3):
|
| 23 |
+
"""Extrae palabras clave usando un método mejorado"""
|
| 24 |
+
# Limpieza de texto y tokenización
|
| 25 |
text = re.sub(r'[^\w\s]', '', text.lower())
|
| 26 |
+
words = re.findall(r'\b\w+\b', text)
|
| 27 |
|
| 28 |
+
# Palabras comunes a excluir (lista ampliada)
|
| 29 |
+
stop_words = {
|
| 30 |
+
"el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "por",
|
| 31 |
+
"un", "una", "con", "se", "del", "al", "lo", "como", "para", "su", "sus"
|
| 32 |
+
}
|
| 33 |
|
| 34 |
+
# Frecuencia de palabras y filtrado
|
| 35 |
word_freq = {}
|
| 36 |
for word in words:
|
| 37 |
if len(word) > 3 and word not in stop_words:
|
| 38 |
word_freq[word] = word_freq.get(word, 0) + 1
|
| 39 |
|
| 40 |
+
# Ordenar por frecuencia y longitud
|
| 41 |
+
sorted_words = sorted(word_freq.items(), key=lambda x: (x[1], len(x[0])), reverse=True)
|
| 42 |
return [word for word, _ in sorted_words[:max_keywords]]
|
| 43 |
|
| 44 |
def search_pexels_videos(keywords, per_query=2):
|
| 45 |
+
"""Busca videos en Pexels con manejo de errores mejorado"""
|
| 46 |
+
if not PEXELS_API_KEY or not keywords:
|
|
|
|
| 47 |
return []
|
| 48 |
|
| 49 |
headers = {"Authorization": PEXELS_API_KEY}
|
|
|
|
| 51 |
|
| 52 |
for query in keywords:
|
| 53 |
try:
|
| 54 |
+
logger.info(f"Buscando videos para: '{query}'")
|
| 55 |
params = {
|
| 56 |
"query": query,
|
| 57 |
"per_page": per_query,
|
|
|
|
| 63 |
"https://api.pexels.com/videos/search",
|
| 64 |
headers=headers,
|
| 65 |
params=params,
|
| 66 |
+
timeout=20
|
| 67 |
)
|
| 68 |
|
| 69 |
if response.status_code == 200:
|
|
|
|
| 79 |
key=lambda x: x.get("width", 0) * x.get("height", 0)
|
| 80 |
)
|
| 81 |
video_urls.append(best_quality["link"])
|
| 82 |
+
logger.info(f"Video encontrado: {best_quality['link']}")
|
| 83 |
+
else:
|
| 84 |
+
logger.warning(f"Respuesta Pexels: {response.status_code}")
|
| 85 |
except Exception as e:
|
| 86 |
+
logger.error(f"Error buscando videos: {str(e)}")
|
| 87 |
|
| 88 |
return video_urls
|
| 89 |
|
| 90 |
async def generate_tts(text, output_path, voice="es-ES-ElviraNeural"):
|
| 91 |
+
"""Genera audio TTS con manejo de errores"""
|
| 92 |
try:
|
| 93 |
communicate = edge_tts.Communicate(text, voice)
|
| 94 |
await communicate.save(output_path)
|
| 95 |
+
logger.info("Audio TTS generado exitosamente")
|
| 96 |
return True
|
| 97 |
except Exception as e:
|
| 98 |
+
logger.error(f"Error en TTS: {str(e)}")
|
| 99 |
return False
|
| 100 |
|
| 101 |
def download_video(url, temp_dir):
|
| 102 |
+
"""Descarga videos con manejo robusto de errores"""
|
| 103 |
try:
|
| 104 |
+
logger.info(f"Descargando video: {url}")
|
| 105 |
+
response = requests.get(url, stream=True, timeout=40)
|
| 106 |
response.raise_for_status()
|
| 107 |
|
| 108 |
+
filename = f"video_{os.getpid()}_{datetime.now().strftime('%H%M%S%f')}.mp4"
|
| 109 |
filepath = os.path.join(temp_dir, filename)
|
| 110 |
|
| 111 |
with open(filepath, 'wb') as f:
|
| 112 |
for chunk in response.iter_content(chunk_size=8192):
|
| 113 |
f.write(chunk)
|
| 114 |
+
|
| 115 |
+
logger.info(f"Video descargado: {filepath}")
|
| 116 |
return filepath
|
| 117 |
except Exception as e:
|
| 118 |
+
logger.error(f"Error descargando video: {str(e)}")
|
| 119 |
return None
|
| 120 |
|
| 121 |
def create_video(audio_path, video_paths, output_path):
|
| 122 |
+
"""Crea el video final con FFmpeg - VERSIÓN CORREGIDA"""
|
| 123 |
try:
|
| 124 |
+
# 1. Crear archivo de lista para concatenación
|
| 125 |
+
list_file_path = os.path.join(os.path.dirname(video_paths[0]), "input.txt")
|
| 126 |
+
with open(list_file_path, "w") as f:
|
| 127 |
for path in video_paths:
|
| 128 |
f.write(f"file '{os.path.basename(path)}'\n")
|
| 129 |
|
| 130 |
+
# 2. Preparar comando FFmpeg
|
|
|
|
|
|
|
|
|
|
| 131 |
cmd = [
|
| 132 |
"ffmpeg", "-y",
|
| 133 |
"-f", "concat",
|
| 134 |
"-safe", "0",
|
| 135 |
+
"-i", list_file_path,
|
| 136 |
"-i", audio_path,
|
| 137 |
+
"-c:v", "libx264", # Codificar video en lugar de copiar
|
| 138 |
+
"-preset", "fast",
|
| 139 |
+
"-crf", "23",
|
| 140 |
"-c:a", "aac",
|
| 141 |
+
"-b:a", "192k",
|
| 142 |
"-shortest",
|
| 143 |
+
"-movflags", "+faststart",
|
| 144 |
output_path
|
| 145 |
]
|
| 146 |
|
| 147 |
+
# 3. Ejecutar FFmpeg con logging detallado
|
| 148 |
+
logger.info("Ejecutando FFmpeg: " + " ".join(cmd))
|
| 149 |
+
result = subprocess.run(
|
| 150 |
+
cmd,
|
| 151 |
+
cwd=os.path.dirname(video_paths[0]),
|
| 152 |
+
stdout=subprocess.PIPE,
|
| 153 |
+
stderr=subprocess.PIPE,
|
| 154 |
+
text=True
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
if result.returncode != 0:
|
| 158 |
+
logger.error(f"Error FFmpeg (code {result.returncode}): {result.stderr}")
|
| 159 |
+
return False
|
| 160 |
+
|
| 161 |
+
logger.info("Video creado exitosamente")
|
| 162 |
return True
|
| 163 |
except Exception as e:
|
| 164 |
+
logger.error(f"Error creando video: {str(e)}")
|
| 165 |
return False
|
| 166 |
finally:
|
| 167 |
+
try:
|
| 168 |
+
if os.path.exists(list_file_path):
|
| 169 |
+
os.remove(list_file_path)
|
| 170 |
+
except:
|
| 171 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
async def generate_video(text, music_file=None):
|
| 174 |
+
"""Función principal con manejo mejorado de errores"""
|
| 175 |
temp_dir = tempfile.mkdtemp()
|
| 176 |
+
logger.info(f"Directorio temporal creado: {temp_dir}")
|
| 177 |
|
| 178 |
try:
|
| 179 |
# 1. Generar audio TTS
|
| 180 |
tts_path = os.path.join(temp_dir, "audio.mp3")
|
| 181 |
if not await generate_tts(text, tts_path):
|
| 182 |
+
return None, "❌ Error generando voz"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
# 2. Extraer palabras clave
|
| 185 |
keywords = extract_keywords(text)
|
|
|
|
|
|
|
| 186 |
if not keywords:
|
| 187 |
+
return None, "❌ No se pudieron extraer palabras clave del texto"
|
| 188 |
+
logger.info(f"Palabras clave identificadas: {keywords}")
|
| 189 |
|
| 190 |
+
# 3. Buscar y descargar videos
|
| 191 |
video_urls = search_pexels_videos(keywords)
|
| 192 |
if not video_urls:
|
| 193 |
+
return None, "❌ No se encontraron videos para las palabras clave"
|
| 194 |
|
| 195 |
video_paths = []
|
| 196 |
for url in video_urls:
|
| 197 |
path = download_video(url, temp_dir)
|
| 198 |
if path:
|
| 199 |
video_paths.append(path)
|
|
|
|
| 200 |
|
| 201 |
if not video_paths:
|
| 202 |
+
return None, "❌ Error descargando videos"
|
| 203 |
|
| 204 |
+
# 4. Crear video final
|
| 205 |
output_path = os.path.join(temp_dir, "final_video.mp4")
|
| 206 |
+
if not create_video(tts_path, video_paths, output_path):
|
| 207 |
+
return None, "❌ Error en la creación del video"
|
| 208 |
+
|
| 209 |
+
return output_path, "✅ Video creado exitosamente"
|
| 210 |
|
| 211 |
except Exception as e:
|
| 212 |
logger.exception("Error inesperado")
|
| 213 |
+
return None, f"❌ Error crítico: {str(e)}"
|
| 214 |
finally:
|
| 215 |
+
# Espacios maneja la limpieza automática
|
| 216 |
pass
|
| 217 |
|
| 218 |
+
# --- Interfaz de Gradio mejorada ---
|
| 219 |
+
with gr.Blocks(title="Generador Automático de Videos", theme=gr.themes.Soft(), css=".gradio-container {max-width: 800px}") as demo:
|
| 220 |
+
gr.Markdown("""
|
| 221 |
+
# 🎬 Generador Automático de Videos con IA
|
| 222 |
+
Transforma texto en videos usando contenido de Pexels y voz sintetizada
|
| 223 |
+
""")
|
| 224 |
|
| 225 |
with gr.Row():
|
| 226 |
+
with gr.Column(scale=2):
|
| 227 |
text_input = gr.Textbox(
|
| 228 |
label="Texto para el video",
|
| 229 |
+
placeholder="Ej: Un hermoso paisaje montañoso con ríos cristalinos...",
|
| 230 |
+
lines=5,
|
| 231 |
+
max_lines=10
|
| 232 |
)
|
| 233 |
+
generate_btn = gr.Button("✨ Generar Video", variant="primary")
|
| 234 |
+
|
| 235 |
+
with gr.Accordion("Configuración avanzada", open=False):
|
| 236 |
+
voice_select = gr.Dropdown(
|
| 237 |
+
["es-ES-ElviraNeural", "es-MX-DaliaNeural", "es-US-AlonsoNeural"],
|
| 238 |
+
label="Voz",
|
| 239 |
+
value="es-ES-ElviraNeural"
|
| 240 |
+
)
|
| 241 |
|
| 242 |
+
with gr.Column(scale=3):
|
| 243 |
+
video_output = gr.Video(
|
| 244 |
+
label="Video Generado",
|
| 245 |
+
interactive=False,
|
| 246 |
+
height=400
|
| 247 |
+
)
|
| 248 |
+
status_output = gr.Textbox(
|
| 249 |
+
label="Estado",
|
| 250 |
+
interactive=False,
|
| 251 |
+
show_label=False,
|
| 252 |
+
container=False
|
| 253 |
+
)
|
| 254 |
|
| 255 |
generate_btn.click(
|
| 256 |
+
fn=lambda: (None, "⏳ Procesando... Esto puede tomar 1-2 minutos"),
|
| 257 |
outputs=[video_output, status_output],
|
| 258 |
queue=False
|
| 259 |
).then(
|
| 260 |
fn=generate_video,
|
| 261 |
+
inputs=[text_input],
|
| 262 |
outputs=[video_output, status_output]
|
| 263 |
)
|
| 264 |
|
| 265 |
+
gr.Markdown("### Instrucciones:")
|
| 266 |
gr.Markdown("""
|
| 267 |
+
1. Describe el video que deseas crear (mínimo 20 palabras)
|
| 268 |
+
2. Haz clic en "Generar Video"
|
| 269 |
+
3. El sistema buscará videos relevantes en Pexels
|
| 270 |
+
4. Creará un video con narración automática
|
|
|
|
| 271 |
""")
|
| 272 |
+
|
| 273 |
+
gr.Markdown("### Ejemplos:")
|
| 274 |
+
examples = gr.Examples(
|
| 275 |
+
examples=[
|
| 276 |
+
["Un atardecer en la playa con palmeras y olas suaves"],
|
| 277 |
+
["Un bosque otoñal con hojas de colores y senderos naturales"],
|
| 278 |
+
["La ciudad de noche con rascacielos iluminados y tráfico"]
|
| 279 |
+
],
|
| 280 |
+
inputs=[text_input],
|
| 281 |
+
label="Ejemplos para probar"
|
| 282 |
+
)
|
| 283 |
|
| 284 |
# Para Hugging Face Spaces
|
| 285 |
if __name__ == "__main__":
|
| 286 |
+
demo.launch(
|
| 287 |
+
server_name="0.0.0.0",
|
| 288 |
+
server_port=7860,
|
| 289 |
+
share=False,
|
| 290 |
+
show_error=True
|
| 291 |
+
)
|