diff --git a/app.py.bak b/backups_viejos/app.py.bak similarity index 100% rename from app.py.bak rename to backups_viejos/app.py.bak diff --git a/app.py.bak_antes_fix_prompts b/backups_viejos/app.py.bak_antes_fix_prompts similarity index 100% rename from app.py.bak_antes_fix_prompts rename to backups_viejos/app.py.bak_antes_fix_prompts diff --git a/app_backup_20260608.py b/backups_viejos/app_backup_20260608.py similarity index 100% rename from app_backup_20260608.py rename to backups_viejos/app_backup_20260608.py diff --git a/app_backup_DIA3_TODO_OK_0224.py b/backups_viejos/app_backup_DIA3_TODO_OK_0224.py similarity index 100% rename from app_backup_DIA3_TODO_OK_0224.py rename to backups_viejos/app_backup_DIA3_TODO_OK_0224.py diff --git a/app_backup_DOS_LEYES_OK_2228.py b/backups_viejos/app_backup_DOS_LEYES_OK_2228.py similarity index 100% rename from app_backup_DOS_LEYES_OK_2228.py rename to backups_viejos/app_backup_DOS_LEYES_OK_2228.py diff --git a/app_backup_EDITOR_COMPLETO_1328.py b/backups_viejos/app_backup_EDITOR_COMPLETO_1328.py similarity index 100% rename from app_backup_EDITOR_COMPLETO_1328.py rename to backups_viejos/app_backup_EDITOR_COMPLETO_1328.py diff --git a/app_backup_GROQ_DURACION_OK_1653.py b/backups_viejos/app_backup_GROQ_DURACION_OK_1653.py similarity index 100% rename from app_backup_GROQ_DURACION_OK_1653.py rename to backups_viejos/app_backup_GROQ_DURACION_OK_1653.py diff --git a/app_backup_NEGRO_ARREGLADO_RAIZ_1348.py b/backups_viejos/app_backup_NEGRO_ARREGLADO_RAIZ_1348.py similarity index 100% rename from app_backup_NEGRO_ARREGLADO_RAIZ_1348.py rename to backups_viejos/app_backup_NEGRO_ARREGLADO_RAIZ_1348.py diff --git a/app_backup_SIN_NEGRO_OK_2034.py b/backups_viejos/app_backup_SIN_NEGRO_OK_2034.py similarity index 100% rename from app_backup_SIN_NEGRO_OK_2034.py rename to backups_viejos/app_backup_SIN_NEGRO_OK_2034.py diff --git a/app_backup_SUGERIR_SEG_OK_0849.py b/backups_viejos/app_backup_SUGERIR_SEG_OK_0849.py similarity index 100% rename from app_backup_SUGERIR_SEG_OK_0849.py rename to backups_viejos/app_backup_SUGERIR_SEG_OK_0849.py diff --git a/app_backup_TIEMPOS_FUNCIONAN_1319.py b/backups_viejos/app_backup_TIEMPOS_FUNCIONAN_1319.py similarity index 100% rename from app_backup_TIEMPOS_FUNCIONAN_1319.py rename to backups_viejos/app_backup_TIEMPOS_FUNCIONAN_1319.py diff --git a/app_backup_VIDEO_E_IMAGEN_OK_1720.py b/backups_viejos/app_backup_VIDEO_E_IMAGEN_OK_1720.py similarity index 100% rename from app_backup_VIDEO_E_IMAGEN_OK_1720.py rename to backups_viejos/app_backup_VIDEO_E_IMAGEN_OK_1720.py diff --git a/app_backup_antes_aclaracion_2245.py b/backups_viejos/app_backup_antes_aclaracion_2245.py similarity index 100% rename from app_backup_antes_aclaracion_2245.py rename to backups_viejos/app_backup_antes_aclaracion_2245.py diff --git a/app_backup_antes_arreglos_1742.py b/backups_viejos/app_backup_antes_arreglos_1742.py similarity index 100% rename from app_backup_antes_arreglos_1742.py rename to backups_viejos/app_backup_antes_arreglos_1742.py diff --git a/app_backup_antes_boton_1909.py b/backups_viejos/app_backup_antes_boton_1909.py similarity index 100% rename from app_backup_antes_boton_1909.py rename to backups_viejos/app_backup_antes_boton_1909.py diff --git a/app_backup_antes_boton_ayuda_2235.py b/backups_viejos/app_backup_antes_boton_ayuda_2235.py similarity index 100% rename from app_backup_antes_boton_ayuda_2235.py rename to backups_viejos/app_backup_antes_boton_ayuda_2235.py diff --git a/app_backup_antes_colores_sub_1032.py b/backups_viejos/app_backup_antes_colores_sub_1032.py similarity index 100% rename from app_backup_antes_colores_sub_1032.py rename to backups_viejos/app_backup_antes_colores_sub_1032.py diff --git a/app_backup_antes_decimales_1054.py b/backups_viejos/app_backup_antes_decimales_1054.py similarity index 100% rename from app_backup_antes_decimales_1054.py rename to backups_viejos/app_backup_antes_decimales_1054.py diff --git a/app_backup_antes_duracion_1940.py b/backups_viejos/app_backup_antes_duracion_1940.py similarity index 100% rename from app_backup_antes_duracion_1940.py rename to backups_viejos/app_backup_antes_duracion_1940.py diff --git a/app_backup_antes_escenas_0906.py b/backups_viejos/app_backup_antes_escenas_0906.py similarity index 100% rename from app_backup_antes_escenas_0906.py rename to backups_viejos/app_backup_antes_escenas_0906.py diff --git a/app_backup_antes_escenas_0912.py b/backups_viejos/app_backup_antes_escenas_0912.py similarity index 100% rename from app_backup_antes_escenas_0912.py rename to backups_viejos/app_backup_antes_escenas_0912.py diff --git a/app_backup_antes_fix_auto_1630.py b/backups_viejos/app_backup_antes_fix_auto_1630.py similarity index 100% rename from app_backup_antes_fix_auto_1630.py rename to backups_viejos/app_backup_antes_fix_auto_1630.py diff --git a/app_backup_antes_fix_manual_2057.py b/backups_viejos/app_backup_antes_fix_manual_2057.py similarity index 100% rename from app_backup_antes_fix_manual_2057.py rename to backups_viejos/app_backup_antes_fix_manual_2057.py diff --git a/app_backup_antes_fix_negro_2019.py b/backups_viejos/app_backup_antes_fix_negro_2019.py similarity index 100% rename from app_backup_antes_fix_negro_2019.py rename to backups_viejos/app_backup_antes_fix_negro_2019.py diff --git a/app_backup_antes_fix_negro_real_1328.py b/backups_viejos/app_backup_antes_fix_negro_real_1328.py similarity index 100% rename from app_backup_antes_fix_negro_real_1328.py rename to backups_viejos/app_backup_antes_fix_negro_real_1328.py diff --git a/app_backup_antes_fix_negro_video_1555.py b/backups_viejos/app_backup_antes_fix_negro_video_1555.py similarity index 100% rename from app_backup_antes_fix_negro_video_1555.py rename to backups_viejos/app_backup_antes_fix_negro_video_1555.py diff --git a/app_backup_antes_fix_voz_1306.py b/backups_viejos/app_backup_antes_fix_voz_1306.py similarity index 100% rename from app_backup_antes_fix_voz_1306.py rename to backups_viejos/app_backup_antes_fix_voz_1306.py diff --git a/app_backup_antes_groq_duracion_1645.py b/backups_viejos/app_backup_antes_groq_duracion_1645.py similarity index 100% rename from app_backup_antes_groq_duracion_1645.py rename to backups_viejos/app_backup_antes_groq_duracion_1645.py diff --git a/app_backup_antes_guia2_1350.py b/backups_viejos/app_backup_antes_guia2_1350.py similarity index 100% rename from app_backup_antes_guia2_1350.py rename to backups_viejos/app_backup_antes_guia2_1350.py diff --git a/app_backup_antes_guia3_1411.py b/backups_viejos/app_backup_antes_guia3_1411.py similarity index 100% rename from app_backup_antes_guia3_1411.py rename to backups_viejos/app_backup_antes_guia3_1411.py diff --git a/app_backup_antes_login_1916.py b/backups_viejos/app_backup_antes_login_1916.py similarity index 100% rename from app_backup_antes_login_1916.py rename to backups_viejos/app_backup_antes_login_1916.py diff --git a/app_backup_antes_ltx_1144.py b/backups_viejos/app_backup_antes_ltx_1144.py similarity index 100% rename from app_backup_antes_ltx_1144.py rename to backups_viejos/app_backup_antes_ltx_1144.py diff --git a/app_backup_antes_modelos_0217.py b/backups_viejos/app_backup_antes_modelos_0217.py similarity index 100% rename from app_backup_antes_modelos_0217.py rename to backups_viejos/app_backup_antes_modelos_0217.py diff --git a/app_backup_antes_montserrat_0813.py b/backups_viejos/app_backup_antes_montserrat_0813.py similarity index 100% rename from app_backup_antes_montserrat_0813.py rename to backups_viejos/app_backup_antes_montserrat_0813.py diff --git a/app_backup_antes_motor_1143.py b/backups_viejos/app_backup_antes_motor_1143.py similarity index 100% rename from app_backup_antes_motor_1143.py rename to backups_viejos/app_backup_antes_motor_1143.py diff --git a/app_backup_antes_quitar_minutos_2229.py b/backups_viejos/app_backup_antes_quitar_minutos_2229.py similarity index 100% rename from app_backup_antes_quitar_minutos_2229.py rename to backups_viejos/app_backup_antes_quitar_minutos_2229.py diff --git a/app_backup_antes_rafaga_1112.py b/backups_viejos/app_backup_antes_rafaga_1112.py similarity index 100% rename from app_backup_antes_rafaga_1112.py rename to backups_viejos/app_backup_antes_rafaga_1112.py diff --git a/app_backup_antes_rafaga_1123.py b/backups_viejos/app_backup_antes_rafaga_1123.py similarity index 100% rename from app_backup_antes_rafaga_1123.py rename to backups_viejos/app_backup_antes_rafaga_1123.py diff --git a/app_backup_antes_rafaga_rapida_1213.py b/backups_viejos/app_backup_antes_rafaga_rapida_1213.py similarity index 100% rename from app_backup_antes_rafaga_rapida_1213.py rename to backups_viejos/app_backup_antes_rafaga_rapida_1213.py diff --git a/app_backup_antes_seg_extra_0927.py b/backups_viejos/app_backup_antes_seg_extra_0927.py similarity index 100% rename from app_backup_antes_seg_extra_0927.py rename to backups_viejos/app_backup_antes_seg_extra_0927.py diff --git a/app_backup_antes_sugerir_seg_0841.py b/backups_viejos/app_backup_antes_sugerir_seg_0841.py similarity index 100% rename from app_backup_antes_sugerir_seg_0841.py rename to backups_viejos/app_backup_antes_sugerir_seg_0841.py diff --git a/app_backup_antes_textos_1320.py b/backups_viejos/app_backup_antes_textos_1320.py similarity index 100% rename from app_backup_antes_textos_1320.py rename to backups_viejos/app_backup_antes_textos_1320.py diff --git a/app_backup_antes_tiempo_1229.py b/backups_viejos/app_backup_antes_tiempo_1229.py similarity index 100% rename from app_backup_antes_tiempo_1229.py rename to backups_viejos/app_backup_antes_tiempo_1229.py diff --git a/app_backup_antes_tiempos_cortos_0204.py b/backups_viejos/app_backup_antes_tiempos_cortos_0204.py similarity index 100% rename from app_backup_antes_tiempos_cortos_0204.py rename to backups_viejos/app_backup_antes_tiempos_cortos_0204.py diff --git a/app_backup_antes_tiempos_motor_1241.py b/backups_viejos/app_backup_antes_tiempos_motor_1241.py similarity index 100% rename from app_backup_antes_tiempos_motor_1241.py rename to backups_viejos/app_backup_antes_tiempos_motor_1241.py diff --git a/app_backup_antes_tope_largo_1335.py b/backups_viejos/app_backup_antes_tope_largo_1335.py similarity index 100% rename from app_backup_antes_tope_largo_1335.py rename to backups_viejos/app_backup_antes_tope_largo_1335.py diff --git a/app_backup_antes_videofondo_2004.py b/backups_viejos/app_backup_antes_videofondo_2004.py similarity index 100% rename from app_backup_antes_videofondo_2004.py rename to backups_viejos/app_backup_antes_videofondo_2004.py diff --git a/app_backup_base_buena_0931.py b/backups_viejos/app_backup_base_buena_0931.py similarity index 100% rename from app_backup_base_buena_0931.py rename to backups_viejos/app_backup_base_buena_0931.py diff --git a/app_backup_boton_escenas_ok_0938.py b/backups_viejos/app_backup_boton_escenas_ok_0938.py similarity index 100% rename from app_backup_boton_escenas_ok_0938.py rename to backups_viejos/app_backup_boton_escenas_ok_0938.py diff --git a/app_backup_escenas_FUNCIONAN_1220.py b/backups_viejos/app_backup_escenas_FUNCIONAN_1220.py similarity index 100% rename from app_backup_escenas_FUNCIONAN_1220.py rename to backups_viejos/app_backup_escenas_FUNCIONAN_1220.py diff --git a/app_backup_escenas_paso1_ok_1056.py b/backups_viejos/app_backup_escenas_paso1_ok_1056.py similarity index 100% rename from app_backup_escenas_paso1_ok_1056.py rename to backups_viejos/app_backup_escenas_paso1_ok_1056.py diff --git a/app_backup_estado_actual_0048.py b/backups_viejos/app_backup_estado_actual_0048.py similarity index 100% rename from app_backup_estado_actual_0048.py rename to backups_viejos/app_backup_estado_actual_0048.py diff --git a/app_backup_fix_auto_OK_1644.py b/backups_viejos/app_backup_fix_auto_OK_1644.py similarity index 100% rename from app_backup_fix_auto_OK_1644.py rename to backups_viejos/app_backup_fix_auto_OK_1644.py diff --git a/app_backup_funciona_2044.py b/backups_viejos/app_backup_funciona_2044.py similarity index 100% rename from app_backup_funciona_2044.py rename to backups_viejos/app_backup_funciona_2044.py diff --git a/app_backup_funciona_2045.py b/backups_viejos/app_backup_funciona_2045.py similarity index 100% rename from app_backup_funciona_2045.py rename to backups_viejos/app_backup_funciona_2045.py diff --git a/app_backup_inicio_dia2_2148.py b/backups_viejos/app_backup_inicio_dia2_2148.py similarity index 100% rename from app_backup_inicio_dia2_2148.py rename to backups_viejos/app_backup_inicio_dia2_2148.py diff --git a/app_backup_inicio_dia3_2317.py b/backups_viejos/app_backup_inicio_dia3_2317.py similarity index 100% rename from app_backup_inicio_dia3_2317.py rename to backups_viejos/app_backup_inicio_dia3_2317.py diff --git a/app_backup_parteA_ok_1200.py b/backups_viejos/app_backup_parteA_ok_1200.py similarity index 100% rename from app_backup_parteA_ok_1200.py rename to backups_viejos/app_backup_parteA_ok_1200.py diff --git a/app_backup_paso2a_ok_1128.py b/backups_viejos/app_backup_paso2a_ok_1128.py similarity index 100% rename from app_backup_paso2a_ok_1128.py rename to backups_viejos/app_backup_paso2a_ok_1128.py diff --git a/app_backup_paso2b_ok_1132.py b/backups_viejos/app_backup_paso2b_ok_1132.py similarity index 100% rename from app_backup_paso2b_ok_1132.py rename to backups_viejos/app_backup_paso2b_ok_1132.py diff --git a/app_backup_seguro.py b/backups_viejos/app_backup_seguro.py similarity index 100% rename from app_backup_seguro.py rename to backups_viejos/app_backup_seguro.py diff --git a/leonardo_service.py.bak b/backups_viejos/leonardo_service.py.bak similarity index 100% rename from leonardo_service.py.bak rename to backups_viejos/leonardo_service.py.bak diff --git a/leonardo_service.py.bak_trad_0712 b/backups_viejos/leonardo_service.py.bak_trad_0712 similarity index 100% rename from leonardo_service.py.bak_trad_0712 rename to backups_viejos/leonardo_service.py.bak_trad_0712 diff --git a/ltx_service.py.bak b/backups_viejos/ltx_service.py.bak similarity index 100% rename from ltx_service.py.bak rename to backups_viejos/ltx_service.py.bak diff --git a/ltx_service.py.bak_20260610_125525 b/backups_viejos/ltx_service.py.bak_20260610_125525 similarity index 100% rename from ltx_service.py.bak_20260610_125525 rename to backups_viejos/ltx_service.py.bak_20260610_125525 diff --git a/ltx_service.py.bak_trad_0712 b/backups_viejos/ltx_service.py.bak_trad_0712 similarity index 100% rename from ltx_service.py.bak_trad_0712 rename to backups_viejos/ltx_service.py.bak_trad_0712 diff --git a/ltx_service_roto_1623.py.bak b/backups_viejos/ltx_service_roto_1623.py.bak similarity index 100% rename from ltx_service_roto_1623.py.bak rename to backups_viejos/ltx_service_roto_1623.py.bak diff --git a/veo_service.py.bak b/backups_viejos/veo_service.py.bak similarity index 100% rename from veo_service.py.bak rename to backups_viejos/veo_service.py.bak diff --git a/requirements.txt b/requirements.txt index 9d4e9289916a5b46e7fee2ec4ddf802e57a35563..c6b9044d4723aa2977d96fd9f128927d5915a945 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,5 @@ google-genai opencv-python-headless gradio_client huggingface_hub +google-auth +google-cloud-aiplatform diff --git a/veo_service.py b/veo_service.py index 3060e959b360a88eab6acd7b2b10d01e511b5a7d..a808dc6aa241ac06c2269e7b2bfed5ddca67f8de 100644 --- a/veo_service.py +++ b/veo_service.py @@ -1,263 +1,98 @@ """ -veo_service.py — Video con RunwayML via RapidAPI (fortunehoppers / aivideoapi). -Optimizado para Prompts Dinámicos en Ultra Calidad 4K y Semilla Aleatoria. +veo_service.py — Servicio independiente de generación de vídeo con Google Veo +Úsalo en app.py con: from veo_service import generar_clips_escenas """ + import os import time -import random # Solución para que los vídeos dejen de ser idénticos - -_KEYS = [] -for _env in ("RAPIDAPI_KEY", "RAPIDAPI_KEY_2", "RAPIDAPI_KEY_3", "RAPIDAPI_KEY_4", "RAPIDAPI_KEY_5"): - _v = os.environ.get(_env, "").strip() - if _v and _v not in _KEYS: - _KEYS.append(_v) - -_AGOTADAS = set() -_HOST = os.environ.get("RAPIDAPI_HOST", "runwayml.p.rapidapi.com").strip() -_MODELO = os.environ.get("RUNWAY_MODELO", "gen3").strip() -_GEN_PATH = os.environ.get("RUNWAY_GEN_PATH", "/generate/text").strip() -_STATUS_PATH = os.environ.get("RUNWAY_STATUS_PATH", "/status").strip() - -try: - _TIME = int(os.environ.get("RUNWAY_TIME", "5")) -except Exception: - _TIME = 5 +# ── Cliente Veo (se inicializa una sola vez al arrancar) ────────────────────── try: - _WAIT = int(os.environ.get("RUNWAY_WAIT", "480")) -except Exception: - _WAIT = 120 - -try: - _MAX_CLIPS = int(os.environ.get("VEO_MAX_CLIPS", "4")) -except Exception: - _MAX_CLIPS = 4 - -def _headers(key): - return { - "x-rapidapi-key": key, - "x-rapidapi-host": _HOST, - "Content-Type": "application/json", - } + from google import genai as _genai + from google.genai import types as _gentypes + _KEY = os.environ.get("GOOGLE_API_KEY", "") + veo_client = _genai.Client(api_key=_KEY) if _KEY else None + if veo_client: + print("[Veo] Cliente inicializado OK", flush=True) + else: + print("[Veo] ⚠️ GOOGLE_API_KEY no encontrada en Secrets", flush=True) +except Exception as _e: + veo_client = None + print(f"[Veo] ⚠️ google-genai no instalado: {_e}", flush=True) + + +def _generar_un_clip(prompt: str, nombre_archivo: str) -> str | None: + """ + Genera un clip .mp4 a partir de un prompt de texto. + Devuelve la ruta del archivo o None si falla. + """ + if not veo_client: + print("[Veo] ❌ Sin cliente, saltando escena", flush=True) + return None -def _es_error_cuota(status_code, data): - if status_code in (401, 403, 429): - return True - s = str(data).lower() - pistas = ("exceeded", "quota", "rate limit", "rate-limit", - "too many requests", "monthly limit", "limit reached", - "upgrade your plan", "not subscribed", "you are not subscribed") - return any(p in s for p in pistas) + # Si ya existe (de una ejecución anterior), reutilizar + if os.path.exists(nombre_archivo) and os.path.getsize(nombre_archivo) > 1000: + print(f"[Veo] ♻️ Reutilizando {nombre_archivo}", flush=True) + return nombre_archivo -_BASE = f"https://{_HOST}" + try: + print(f"[Veo] 🎬 Generando: {prompt[:70]}...", flush=True) + operation = veo_client.models.generate_videos( + model="veo-3.1-fast-generate-preview", + prompt=prompt, + config=_gentypes.GenerateVideosConfig( + aspect_ratio="9:16", + resolution="720p", + ), + ) + + # Polling hasta que termine (máx 5 min) + t0 = time.time() + while not operation.done: + if time.time() - t0 > 300: + print("[Veo] ❌ Timeout (5 min)", flush=True) + return None + time.sleep(10) + operation = veo_client.operations.get(operation) + + if not operation.response or not operation.response.generated_videos: + print("[Veo] ❌ Respuesta vacía de la API", flush=True) + return None -if bool(_KEYS): - print(f"[Veo] RunwayML(RapidAPI) OK | host={_HOST} | modelo={_MODELO} | keys={len(_KEYS)} | max_clips={_MAX_CLIPS}", flush=True) -else: - print("[Veo] Falta RAPIDAPI_KEY", flush=True) + video = operation.response.generated_videos[0] + veo_client.files.download(file=video.video) + video.video.save(nombre_archivo) + print(f"[Veo] ✅ Guardado: {nombre_archivo}", flush=True) + return nombre_archivo -_GLOSARIO = { - "teshua": "a luminous silhouette of a soul ascending back toward a radiant divine light source, soft golden rays", - "yeshua": "a glowing backlit silhouette of a robed figure with open arms, no detailed face, soft halo of light", - "jesus": "a glowing backlit silhouette of a robed figure with open arms, no detailed face, soft halo of light", - "cristo": "a glowing backlit silhouette of a robed figure, no detailed face, warm divine light behind", - "dios": "celestial light descending through clouds, divine radiance, soft god rays", - "padre": "vast warm light from above, gentle divine radiance through clouds", - "alma": "a glowing inner light, floating luminous particles, ethereal essence drifting upward", - "espiritu": "soft drifting light, mist and glowing particles floating in the air", - "luz": "warm radiant light flooding the scene, glowing ethereal atmosphere", - "fuente": "a radiant source of light glowing on the horizon, the origin of all light", - "angel": "a luminous winged silhouette backlit by soft light, no detailed face", - "cielo": "a vast glowing sky, soft luminous clouds, divine light breaking through", - "renacer": "dawn breaking, the first warm light rising over a horizon, sense of rebirth", - "renacimiento": "dawn light rising over a calm horizon, gentle rebirth, soft glow", - "camino": "a glowing path winding toward a distant radiant light", - "oracion": "a praying silhouette bathed in soft warm light, gentle glow", - "corazon": "a soft glowing light pulsing gently like a heartbeat, warm aura", - "eternidad": "an endless cosmos of soft stars and slow drifting light", - "agua": "calm reflective water shimmering with soft light, slow gentle ripples", - "desierto": "a vast serene desert under soft golden light, slow cinematic camera", -} + except Exception as e: + print(f"[Veo] ❌ Error: {e}", flush=True) + return None -def _pistas_glosario(texto): - t = texto.lower() - pistas = [] - for clave, pista in _GLOSARIO.items(): - if clave in t and pista not in pistas: - pistas.append(pista) - return pistas[:3] -def _texto_a_prompt_visual(narracion): - claves = [k for k in [os.environ.get("GROQ_KEY", ""), - os.environ.get("GROQ_KEY_2", ""), - os.environ.get("GROQ_KEY_3", "")] if k] - if not claves: - return narracion - - pistas = _pistas_glosario(narracion) - extra = "" - if pistas: - extra = ("\n\nEste texto trata temas espirituales. Apoyate en simbolos visuales como: " - + "; ".join(pistas) + ".") - - instr = ( - "Eres un director de fotografia experto en videos verticales (9:16). Adapta el estilo visual estrictamente al nicho del texto: 1) Si el texto es comercial, de negocios o marketing, crea prompts modernos, limpios, elegantes y de alta calidad corporativa. 2) Si el texto es de misterio, espiritual o esoterico, usa esteticas misticas, nieblas celestiales, simbolos y elementos sagrados.\n" - "Convierte la frase en una descripcion visual cinematografica breve " - "(maximo 30 palabras), escrita en INGLES.\n" - "Reglas obligatorias de calidad:\n" - "- Añade SIEMPRE al final términos de ultra calidad: '4k resolution, hyper-detailed, crystal clear, sharp focus, high contrast, cinematic lighting'.\n" - "- Traduce las ideas abstractas en SIMBOLOS visuales: luz, siluetas, naturaleza, cosmos, amanecer, agua, caminos, umbrales.\n" - "- Para cualquier figura humana o divina usa SIEMPRE una silueta a contraluz o una figura de luz, NUNCA un rostro detallado.\n" - "- Atmosfera eterea, luz calida and suave, movimiento de camara lento.\n" - "- Nada de texto en pantalla ni logotipos.\n" - "- Responde SOLO la descripcion visual en ingles, sin comillas ni explicaciones." - + extra + - "\n\nFrase: " + narracion - ) - try: - from groq import Groq - for k in claves: - try: - r = Groq(api_key=k).chat.completions.create( - messages=[{"role": "user", "content": instr}], - model="llama-3.1-8b-instant", temperature=0.85) - p = r.choices[0].message.content.strip().strip('"').strip() - if p: - return p[:400] - except Exception: - continue - except Exception: - pass - return narracion +def generar_clips_escenas(escenas: list[dict]) -> list[dict]: + """ + Recibe la lista de escenas de Fénix y devuelve la misma lista + con el campo 'material' rellenado para las escenas sin material. -def _buscar_url(obj): - if isinstance(obj, str): - s = obj.lower() - if obj.startswith("http") and (".mp4" in s or "video" in s or "/files" in s): - return obj - return None - if isinstance(obj, dict): - for v in obj.values(): - u = _buscar_url(v) - if u: return u - if isinstance(obj, list): - for v in obj: - u = _buscar_url(v) - if u: return u - return None + Cada escena es un dict: {"material": "", "segundos": 5, "texto": "..."} -def _post_generar(headers, prompt): - semilla_aleatoria = random.randint(1, 99999999) - body = { - "text_prompt": prompt, - "model": _MODELO, - "width": 768, - "height": 1280, - "motion": 5, - "seed": semilla_aleatoria, - "time": _TIME, - "callback_url": "", - } - import requests - try: - r = requests.post(_BASE + _GEN_PATH, json=body, headers=headers, timeout=60) - except Exception as ex: - print(f"[Veo] Error de red al generar: {ex}", flush=True) - return None, "fallo" - try: - data = r.json() - except Exception: - print(f"[Veo] Respuesta no-JSON ({r.status_code}): {r.text[:200]}", flush=True) - if _es_error_cuota(r.status_code, r.text): - return None, "cuota" - return None, "fallo" - if r.status_code >= 400: - if _es_error_cuota(r.status_code, data): - return None, "cuota" - print(f"[Veo] Error {r.status_code} al generar: {str(data)[:200]}", flush=True) - return None, "fallo" - - uuid = data.get("uuid") or data.get("task_id") or data.get("id") - if not uuid: - print(f"[Veo] No vino uuid: {str(data)[:200]}", flush=True) - return None, "fallo" - return uuid, "ok" + Uso en app.py: + from veo_service import generar_clips_escenas + escenas = generar_clips_escenas(escenas) + """ + resultado = [] + for i, escena in enumerate(escenas): + mat = escena.get("material", "").strip() + texto = escena.get("texto", "").strip() + seg = int(escena.get("segundos", 5) or 5) -def _poll_descargar(headers, uuid, nombre): - import requests - t0 = time.time() - intentos = 0 - while time.time() - t0 < _WAIT: - time.sleep(12) - intentos += 1 - try: - s = requests.get(_BASE + _STATUS_PATH, params={"uuid": uuid}, headers=headers, timeout=60) - sd = s.json() - except Exception as ex: - print(f"[Veo] estado intento {intentos}: fallo al consultar ({ex})", flush=True) - continue - if intentos <= 6: - print(f"[Veo] estado intento {intentos} (HTTP {s.status_code}): {str(sd)[:300]}", flush=True) - - estado = str(sd.get("status", "")).lower() - url = _buscar_url(sd) - if url: - vid = requests.get(url, timeout=180) - if vid.status_code == 200 and len(vid.content) > 1000: - with open(nombre, "wb") as f: - f.write(vid.content) - print(f"[Veo] Guardado (bytes): {nombre}", flush=True) - return nombre - if estado in ("failed", "error", "rejected"): - print(f"[Veo] Falló: {str(sd)[:200]}", flush=True) - return None - print("[Veo] Timeout esperando el video", flush=True) - return None + # Solo llamar a Veo si: no hay material Y hay texto Y hay cliente + if not mat and texto and veo_client: + nombre = f"veo_{i}.mp4" + clip = _generar_un_clip(texto, nombre) + mat = clip if clip else "" -def _generar_un_clip(prompt, nombre): - if not bool(_KEYS): - print("[Veo] Sin RAPIDAPI_KEY, salto escena", flush=True) - return None - if os.path.exists(nombre) and os.path.getsize(nombre) > 1000: - print(f"[Veo] Reutilizando {nombre}", flush=True) - return nombre - print(f"[Veo] Generando ({_MODELO}) {prompt[:70]}...", flush=True) - disponibles = [k for k in _KEYS if k not in _AGOTADAS] - if not disponibles: - print("[Veo] Todas las API keys estan agotadas en esta ejecucion", flush=True) - return None - for idx, key in enumerate(_KEYS, 1): - if key in _AGOTADAS: - continue - uuid, estado = _post_generar(_headers(key), prompt) - if estado == "cuota": - _AGOTADAS.add(key) - print(f"[Veo] Key #{idx} no disponible (cuota/limite). Probando siguiente...", flush=True) - continue - if estado != "ok": - print(f"[Veo] Key #{idx} fallo al generar. Probando siguiente...", flush=True) - continue - print(f"[Veo] Key #{idx}: en cola, uuid={uuid}. Esperando...", flush=True) - res = _poll_descargar(_headers(key), uuid, nombre) - if res: - return res - return None - print("[Veo] Sin API keys con cuota disponible", flush=True) - return None + resultado.append({"material": mat, "segundos": seg, "texto": texto}) -def generar_clips_escenas(escenas): - res = [] - gen = 0 - for i, e in enumerate(escenas): - mat = e.get("material", "").strip() - txt = e.get("texto", "").strip() - seg = int(e.get("segundos", 5) or 5) - if not mat and txt and bool(_KEYS) and gen < _MAX_CLIPS: - clip = _generar_un_clip(_texto_a_prompt_visual(txt), f"veo_{i}.mp4") - if clip: - mat = clip - gen += 1 - res.append({"material": mat, "segundos": seg, "texto": txt}) - if gen >= _MAX_CLIPS: - print(f"[Veo] Tope de {_MAX_CLIPS} clips (resto usa tu material/Pexels)", flush=True) - return res + return resultado diff --git a/veo_service_RUNWAY.py.bak b/veo_service_RUNWAY.py.bak new file mode 100644 index 0000000000000000000000000000000000000000..3060e959b360a88eab6acd7b2b10d01e511b5a7d --- /dev/null +++ b/veo_service_RUNWAY.py.bak @@ -0,0 +1,263 @@ +""" +veo_service.py — Video con RunwayML via RapidAPI (fortunehoppers / aivideoapi). +Optimizado para Prompts Dinámicos en Ultra Calidad 4K y Semilla Aleatoria. +""" +import os +import time +import random # Solución para que los vídeos dejen de ser idénticos + +_KEYS = [] +for _env in ("RAPIDAPI_KEY", "RAPIDAPI_KEY_2", "RAPIDAPI_KEY_3", "RAPIDAPI_KEY_4", "RAPIDAPI_KEY_5"): + _v = os.environ.get(_env, "").strip() + if _v and _v not in _KEYS: + _KEYS.append(_v) + +_AGOTADAS = set() +_HOST = os.environ.get("RAPIDAPI_HOST", "runwayml.p.rapidapi.com").strip() +_MODELO = os.environ.get("RUNWAY_MODELO", "gen3").strip() +_GEN_PATH = os.environ.get("RUNWAY_GEN_PATH", "/generate/text").strip() +_STATUS_PATH = os.environ.get("RUNWAY_STATUS_PATH", "/status").strip() + +try: + _TIME = int(os.environ.get("RUNWAY_TIME", "5")) +except Exception: + _TIME = 5 + +try: + _WAIT = int(os.environ.get("RUNWAY_WAIT", "480")) +except Exception: + _WAIT = 120 + +try: + _MAX_CLIPS = int(os.environ.get("VEO_MAX_CLIPS", "4")) +except Exception: + _MAX_CLIPS = 4 + +def _headers(key): + return { + "x-rapidapi-key": key, + "x-rapidapi-host": _HOST, + "Content-Type": "application/json", + } + +def _es_error_cuota(status_code, data): + if status_code in (401, 403, 429): + return True + s = str(data).lower() + pistas = ("exceeded", "quota", "rate limit", "rate-limit", + "too many requests", "monthly limit", "limit reached", + "upgrade your plan", "not subscribed", "you are not subscribed") + return any(p in s for p in pistas) + +_BASE = f"https://{_HOST}" + +if bool(_KEYS): + print(f"[Veo] RunwayML(RapidAPI) OK | host={_HOST} | modelo={_MODELO} | keys={len(_KEYS)} | max_clips={_MAX_CLIPS}", flush=True) +else: + print("[Veo] Falta RAPIDAPI_KEY", flush=True) + +_GLOSARIO = { + "teshua": "a luminous silhouette of a soul ascending back toward a radiant divine light source, soft golden rays", + "yeshua": "a glowing backlit silhouette of a robed figure with open arms, no detailed face, soft halo of light", + "jesus": "a glowing backlit silhouette of a robed figure with open arms, no detailed face, soft halo of light", + "cristo": "a glowing backlit silhouette of a robed figure, no detailed face, warm divine light behind", + "dios": "celestial light descending through clouds, divine radiance, soft god rays", + "padre": "vast warm light from above, gentle divine radiance through clouds", + "alma": "a glowing inner light, floating luminous particles, ethereal essence drifting upward", + "espiritu": "soft drifting light, mist and glowing particles floating in the air", + "luz": "warm radiant light flooding the scene, glowing ethereal atmosphere", + "fuente": "a radiant source of light glowing on the horizon, the origin of all light", + "angel": "a luminous winged silhouette backlit by soft light, no detailed face", + "cielo": "a vast glowing sky, soft luminous clouds, divine light breaking through", + "renacer": "dawn breaking, the first warm light rising over a horizon, sense of rebirth", + "renacimiento": "dawn light rising over a calm horizon, gentle rebirth, soft glow", + "camino": "a glowing path winding toward a distant radiant light", + "oracion": "a praying silhouette bathed in soft warm light, gentle glow", + "corazon": "a soft glowing light pulsing gently like a heartbeat, warm aura", + "eternidad": "an endless cosmos of soft stars and slow drifting light", + "agua": "calm reflective water shimmering with soft light, slow gentle ripples", + "desierto": "a vast serene desert under soft golden light, slow cinematic camera", +} + +def _pistas_glosario(texto): + t = texto.lower() + pistas = [] + for clave, pista in _GLOSARIO.items(): + if clave in t and pista not in pistas: + pistas.append(pista) + return pistas[:3] + +def _texto_a_prompt_visual(narracion): + claves = [k for k in [os.environ.get("GROQ_KEY", ""), + os.environ.get("GROQ_KEY_2", ""), + os.environ.get("GROQ_KEY_3", "")] if k] + if not claves: + return narracion + + pistas = _pistas_glosario(narracion) + extra = "" + if pistas: + extra = ("\n\nEste texto trata temas espirituales. Apoyate en simbolos visuales como: " + + "; ".join(pistas) + ".") + + instr = ( + "Eres un director de fotografia experto en videos verticales (9:16). Adapta el estilo visual estrictamente al nicho del texto: 1) Si el texto es comercial, de negocios o marketing, crea prompts modernos, limpios, elegantes y de alta calidad corporativa. 2) Si el texto es de misterio, espiritual o esoterico, usa esteticas misticas, nieblas celestiales, simbolos y elementos sagrados.\n" + "Convierte la frase en una descripcion visual cinematografica breve " + "(maximo 30 palabras), escrita en INGLES.\n" + "Reglas obligatorias de calidad:\n" + "- Añade SIEMPRE al final términos de ultra calidad: '4k resolution, hyper-detailed, crystal clear, sharp focus, high contrast, cinematic lighting'.\n" + "- Traduce las ideas abstractas en SIMBOLOS visuales: luz, siluetas, naturaleza, cosmos, amanecer, agua, caminos, umbrales.\n" + "- Para cualquier figura humana o divina usa SIEMPRE una silueta a contraluz o una figura de luz, NUNCA un rostro detallado.\n" + "- Atmosfera eterea, luz calida and suave, movimiento de camara lento.\n" + "- Nada de texto en pantalla ni logotipos.\n" + "- Responde SOLO la descripcion visual en ingles, sin comillas ni explicaciones." + + extra + + "\n\nFrase: " + narracion + ) + try: + from groq import Groq + for k in claves: + try: + r = Groq(api_key=k).chat.completions.create( + messages=[{"role": "user", "content": instr}], + model="llama-3.1-8b-instant", temperature=0.85) + p = r.choices[0].message.content.strip().strip('"').strip() + if p: + return p[:400] + except Exception: + continue + except Exception: + pass + return narracion + +def _buscar_url(obj): + if isinstance(obj, str): + s = obj.lower() + if obj.startswith("http") and (".mp4" in s or "video" in s or "/files" in s): + return obj + return None + if isinstance(obj, dict): + for v in obj.values(): + u = _buscar_url(v) + if u: return u + if isinstance(obj, list): + for v in obj: + u = _buscar_url(v) + if u: return u + return None + +def _post_generar(headers, prompt): + semilla_aleatoria = random.randint(1, 99999999) + body = { + "text_prompt": prompt, + "model": _MODELO, + "width": 768, + "height": 1280, + "motion": 5, + "seed": semilla_aleatoria, + "time": _TIME, + "callback_url": "", + } + import requests + try: + r = requests.post(_BASE + _GEN_PATH, json=body, headers=headers, timeout=60) + except Exception as ex: + print(f"[Veo] Error de red al generar: {ex}", flush=True) + return None, "fallo" + try: + data = r.json() + except Exception: + print(f"[Veo] Respuesta no-JSON ({r.status_code}): {r.text[:200]}", flush=True) + if _es_error_cuota(r.status_code, r.text): + return None, "cuota" + return None, "fallo" + if r.status_code >= 400: + if _es_error_cuota(r.status_code, data): + return None, "cuota" + print(f"[Veo] Error {r.status_code} al generar: {str(data)[:200]}", flush=True) + return None, "fallo" + + uuid = data.get("uuid") or data.get("task_id") or data.get("id") + if not uuid: + print(f"[Veo] No vino uuid: {str(data)[:200]}", flush=True) + return None, "fallo" + return uuid, "ok" + +def _poll_descargar(headers, uuid, nombre): + import requests + t0 = time.time() + intentos = 0 + while time.time() - t0 < _WAIT: + time.sleep(12) + intentos += 1 + try: + s = requests.get(_BASE + _STATUS_PATH, params={"uuid": uuid}, headers=headers, timeout=60) + sd = s.json() + except Exception as ex: + print(f"[Veo] estado intento {intentos}: fallo al consultar ({ex})", flush=True) + continue + if intentos <= 6: + print(f"[Veo] estado intento {intentos} (HTTP {s.status_code}): {str(sd)[:300]}", flush=True) + + estado = str(sd.get("status", "")).lower() + url = _buscar_url(sd) + if url: + vid = requests.get(url, timeout=180) + if vid.status_code == 200 and len(vid.content) > 1000: + with open(nombre, "wb") as f: + f.write(vid.content) + print(f"[Veo] Guardado (bytes): {nombre}", flush=True) + return nombre + if estado in ("failed", "error", "rejected"): + print(f"[Veo] Falló: {str(sd)[:200]}", flush=True) + return None + print("[Veo] Timeout esperando el video", flush=True) + return None + +def _generar_un_clip(prompt, nombre): + if not bool(_KEYS): + print("[Veo] Sin RAPIDAPI_KEY, salto escena", flush=True) + return None + if os.path.exists(nombre) and os.path.getsize(nombre) > 1000: + print(f"[Veo] Reutilizando {nombre}", flush=True) + return nombre + print(f"[Veo] Generando ({_MODELO}) {prompt[:70]}...", flush=True) + disponibles = [k for k in _KEYS if k not in _AGOTADAS] + if not disponibles: + print("[Veo] Todas las API keys estan agotadas en esta ejecucion", flush=True) + return None + for idx, key in enumerate(_KEYS, 1): + if key in _AGOTADAS: + continue + uuid, estado = _post_generar(_headers(key), prompt) + if estado == "cuota": + _AGOTADAS.add(key) + print(f"[Veo] Key #{idx} no disponible (cuota/limite). Probando siguiente...", flush=True) + continue + if estado != "ok": + print(f"[Veo] Key #{idx} fallo al generar. Probando siguiente...", flush=True) + continue + print(f"[Veo] Key #{idx}: en cola, uuid={uuid}. Esperando...", flush=True) + res = _poll_descargar(_headers(key), uuid, nombre) + if res: + return res + return None + print("[Veo] Sin API keys con cuota disponible", flush=True) + return None + +def generar_clips_escenas(escenas): + res = [] + gen = 0 + for i, e in enumerate(escenas): + mat = e.get("material", "").strip() + txt = e.get("texto", "").strip() + seg = int(e.get("segundos", 5) or 5) + if not mat and txt and bool(_KEYS) and gen < _MAX_CLIPS: + clip = _generar_un_clip(_texto_a_prompt_visual(txt), f"veo_{i}.mp4") + if clip: + mat = clip + gen += 1 + res.append({"material": mat, "segundos": seg, "texto": txt}) + if gen >= _MAX_CLIPS: + print(f"[Veo] Tope de {_MAX_CLIPS} clips (resto usa tu material/Pexels)", flush=True) + return res