news3 / app.py
salomonsky's picture
Update app.py
d42851a verified
raw
history blame
9.45 kB
import gradio as gr
import google.generativeai as genai
from gtts import gTTS
import os
from moviepy.editor import ImageSequenceClip, AudioFileClip, concatenate_videoclips
from PIL import Image, ImageOps
# Configura tu API Key de Gemini
# Asegúrate de tener esta clave en tus variables de entorno o un archivo .env
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY:
genai.configure(api_key=GEMINI_API_KEY)
else:
print("Advertencia: La GEMINI_API_KEY no está configurada. La generación de texto con Gemini no estará disponible.")
# --- Funciones de Ayuda ---
def generate_news_with_gemini(prompt):
"""Genera una noticia usando la API de Gemini."""
if not GEMINI_API_KEY:
return "Error: GEMINI_API_KEY no está configurada para generar texto."
try:
model = genai.GenerativeModel('gemini-pro')
response = model.generate_content(prompt)
return response.text
except Exception as e:
return f"Error al generar la noticia con Gemini: {e}"
def text_to_speech(text, output_filename="audio.mp3"):
"""Convierte texto a voz usando gTTS."""
try:
tts = gTTS(text=text, lang='es') # Puedes cambiar 'es' por el idioma que desees
tts.save(output_filename)
return output_filename
except Exception as e:
raise Exception(f"Error al generar el audio: {e}")
def process_images(image_paths, video_duration, video_aspect_ratio="16:9"):
"""
Procesa las imágenes: redimensiona, recorta y ajusta para el video.
Devuelve una lista de rutas a las imágenes procesadas y su duración por imagen.
"""
processed_image_folder = "temp_processed_images"
os.makedirs(processed_image_folder, exist_ok=True)
target_width, target_height = (1280, 720) if video_aspect_ratio == "16:9" else (720, 1280)
num_images = len(image_paths)
if num_images == 0:
raise ValueError("No se subieron imágenes.")
# Calcular la duración de cada imagen
image_duration = video_duration / num_images
processed_image_files = []
for i, img_path in enumerate(image_paths):
try:
img = Image.open(img_path).convert("RGB")
# Calcular las dimensiones para recortar y ajustar
original_width, original_height = img.size
target_ratio = target_width / target_height
image_ratio = original_width / original_height
if image_ratio > target_ratio:
# La imagen es más ancha que el objetivo, se recortará horizontalmente
new_width = int(original_height * target_ratio)
left = (original_width - new_width) / 2
top = 0
right = (original_width + new_width) / 2
bottom = original_height
else:
# La imagen es más alta que el objetivo, se recortará verticalmente
new_height = int(original_width / target_ratio)
left = 0
top = (original_height - new_height) / 2
right = original_width
bottom = (original_height + new_height) / 2
img = img.crop((left, top, right, bottom))
img = img.resize((target_width, target_height), Image.Resampling.LANCZOS) # Mejor calidad de redimensionamiento
output_path = os.path.join(processed_image_folder, f"processed_image_{i:03d}.png")
img.save(output_path)
processed_image_files.append(output_path)
except Exception as e:
print(f"Error procesando imagen {img_path}: {e}")
continue
return processed_image_files, image_duration
def create_video(audio_path, processed_image_files, image_duration, video_aspect_ratio="16:9"):
"""Crea un video combinando el audio y la secuencia de imágenes."""
try:
if not os.path.exists(audio_path):
raise FileNotFoundError(f"El archivo de audio no se encontró: {audio_path}")
if not processed_image_files:
raise ValueError("No hay imágenes procesadas para crear el video.")
audio_clip = AudioFileClip(audio_path)
# Crear clips de imagen para la duración del audio
image_clips = []
for img_file in processed_image_files:
clip = ImageSequenceClip([img_file], fps=1/image_duration) # 1 imagen por image_duration segundos
image_clips.append(clip.set_duration(image_duration)) # Asegura que cada imagen tenga su duración calculada
# Concatenar todos los clips de imagen
final_video_clip = concatenate_videoclips(image_clips, method="compose")
# Ajustar la duración del video al audio y añadir el audio
final_video_clip = final_video_clip.set_audio(audio_clip)
final_video_clip = final_video_clip.set_duration(audio_clip.duration)
output_video_filename = "output_news_video.mp4"
final_video_clip.write_videofile(output_video_filename, fps=24, codec="libx264", audio_codec="aac")
return output_video_filename
except Exception as e:
raise Exception(f"Error al crear el video: {e}")
# --- Función Principal de Gradio ---
def create_news_video(news_text_input, gemini_prompt, image_files, video_ratio):
"""
Función principal que orquesta la generación de la noticia, audio y video.
"""
news_content = ""
output_video_path = None
error_message = ""
# 1. Obtener la Noticia
if news_text_input:
news_content = news_text_input
elif gemini_prompt:
news_content = generate_news_with_gemini(gemini_prompt)
if "Error" in news_content:
return news_content, None # Devuelve el error directamente si Gemini falla
else:
return "Por favor, escribe una noticia o proporciona un prompt para Gemini.", None
if not news_content:
return "No se pudo obtener el contenido de la noticia.", None
try:
# 2. Generar Audio de la Noticia
audio_file_path = text_to_speech(news_content)
audio_duration = AudioFileClip(audio_file_path).duration
# 3. Procesar Imágenes
if not image_files:
return "Por favor, sube al menos una imagen.", None
# Gradio `gr.File(file_count="multiple")` devuelve una lista de diccionarios
# donde cada diccionario tiene la clave 'name' con la ruta temporal del archivo.
image_paths = [img.name for img in image_files]
processed_images, img_duration_per_image = process_images(
image_paths, audio_duration, video_aspect_ratio=video_ratio
)
if not processed_images:
return "Error: No se pudieron procesar las imágenes.", None
# 4. Crear Video
output_video_path = create_video(audio_file_path, processed_images, img_duration_per_image, video_ratio)
# Limpiar archivos temporales (opcional, pero buena práctica)
os.remove(audio_file_path)
for img_file in processed_images:
os.remove(img_file)
os.rmdir("temp_processed_images")
return "Video generado con éxito.", output_video_path
except Exception as e:
error_message = f"Ocurrió un error inesperado: {e}"
# Limpieza en caso de error
if os.path.exists("audio.mp3"):
os.remove("audio.mp3")
if os.path.exists("temp_processed_images"):
for f in os.listdir("temp_processed_images"):
os.remove(os.path.join("temp_processed_images", f))
os.rmdir("temp_processed_images")
return error_message, None
# --- Interfaz de Gradio ---
with gr.Blocks() as demo:
gr.Markdown(
"""
# 🎥 Creador de Videos de Noticias con IA y Gradio 🎥
Escribe una noticia, súbe tus imágenes y deja que la IA genere un video para ti.
Puedes escribir la noticia directamente o generarla con tu **GEMINI_API_KEY**.
"""
)
with gr.Row():
with gr.Column():
news_input = gr.Textbox(
label="Escribe tu noticia aquí",
placeholder="Ej: Un nuevo descubrimiento científico...",
lines=5
)
gemini_prompt_input = gr.Textbox(
label="O genera la noticia con Gemini (Escribe un prompt)",
placeholder="Ej: Escribe una noticia sobre un nuevo récord mundial de ajedrez."
)
image_upload = gr.File(
label="Sube de 1 a 60 imágenes (JPG, PNG)",
file_count="multiple",
type="filepath",
file_types=[".jpg", ".jpeg", ".png"],
max_files=60
)
video_ratio_dropdown = gr.Dropdown(
label="Relación de Aspecto del Video",
choices=["16:9", "9:16"],
value="16:9"
)
generate_button = gr.Button("Generar Video de Noticia")
with gr.Column():
output_message = gr.Textbox(label="Estado", interactive=False)
video_output = gr.Video(label="Video de la Noticia")
generate_button.click(
fn=create_news_video,
inputs=[news_input, gemini_prompt_input, image_upload, video_ratio_dropdown],
outputs=[output_message, video_output]
)
if __name__ == "__main__":
demo.launch()