Spaces:
Running
Running
File size: 8,043 Bytes
edb354a ffd7703 edb354a 80d9746 0510ab1 80d9746 ffd7703 edb354a ffd7703 d42851a ffd7703 60edbd8 fc310b8 80d9746 fc310b8 80d9746 fc310b8 cea3c60 80d9746 cea3c60 80d9746 cc42f3e cea3c60 6273d7d 80d9746 c9448ed 6273d7d 80d9746 ffd7703 80d9746 ffd7703 80d9746 ffd7703 80d9746 78756c2 80d9746 78756c2 80d9746 78756c2 80d9746 6273d7d 80d9746 fc310b8 80d9746 fc310b8 80d9746 fc310b8 ffd7703 80d9746 bebc0f4 d42851a ffd7703 80d9746 b85e3c3 ffd7703 80d9746 b85e3c3 80d9746 cc42f3e b85e3c3 80d9746 fa691f6 80d9746 fa691f6 ffd7703 cea3c60 ffd7703 edb354a cc42f3e 80d9746 6273d7d 80d9746 cc42f3e 80d9746 c92ef96 d42851a 80d9746 cea3c60 80d9746 fa691f6 d42851a 80d9746 ffd7703 80d9746 d42851a b85e3c3 ffd7703 b85e3c3 cea3c60 ffd7703 d42851a edb354a fa691f6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
import gradio as gr
from gtts import gTTS
import os
from PIL import Image
from pydub import AudioSegment
import subprocess
import shutil
import math
def text_to_speech(text: str, output_filename="audio.mp3"):
try:
tts = gTTS(text=text, lang='es')
tts.save(output_filename)
return output_filename
except Exception as e:
raise Exception(f"Error al generar el audio con gTTS: {e}")
def get_audio_duration(audio_path):
if not os.path.exists(audio_path) or os.path.getsize(audio_path) == 0:
return 0
try:
audio = AudioSegment.from_file(audio_path)
return audio.duration_seconds
except Exception as e:
raise Exception(f"Error al obtener la duración del audio: {e}")
def process_image(img_path, target_width, target_height, output_folder, index):
try:
img = Image.open(img_path).convert("RGB")
original_width, original_height = img.size
target_ratio = target_width / target_height
image_ratio = original_width / original_height
if image_ratio > target_ratio:
new_width = int(original_height * target_ratio)
left = (original_width - new_width) / 2
img = img.crop((left, 0, left + new_width, original_height))
elif image_ratio < target_ratio:
new_height = int(original_width / target_ratio)
top = (original_height - new_height) / 2
img = img.crop((0, top, original_width, top + new_height))
img = img.resize((target_width, target_height), Image.Resampling.LANCZOS)
output_path = os.path.join(output_folder, f"processed_image_{index:03d}.png")
img.save(output_path)
return output_path
except Exception as e:
return None
def create_video_with_ken_burns(processed_images, audio_duration, fps, video_size, output_filename):
if not processed_images:
raise ValueError("No hay imágenes procesadas para crear el video.")
IMAGE_DURATION = 3
num_images = len(processed_images)
width, height = video_size
num_loops = math.ceil(audio_duration / (num_images * IMAGE_DURATION)) if (num_images * IMAGE_DURATION) > 0 else 1
filter_complex_chains = []
video_clips = []
total_clips = num_images * num_loops
input_commands = []
for img_path in processed_images * num_loops:
input_commands.extend(["-i", img_path])
for i in range(total_clips):
zoom = 1.2
filter_complex_chains.append(f"[{i}:v]scale={width*zoom}:{height*zoom},zoompan=z='min(zoom+0.0015,1.5)':d={fps*IMAGE_DURATION}:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s={width}x{height},fade=t=in:st=0:d=1,fade=t=out:st={IMAGE_DURATION-1}:d=1[v{i}]")
video_clips.append(f"[v{i}]")
concat_filter = f"{''.join(video_clips)}concat=n={total_clips}:v=1:a=0,format=yuv420p[v]"
filter_complex = ";".join(filter_complex_chains) + ";" + concat_filter
command = ["ffmpeg", "-y"]
command.extend(input_commands)
command.extend([
"-filter_complex", filter_complex,
"-map", "[v]",
"-t", str(audio_duration),
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
output_filename
])
try:
subprocess.run(command, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
raise Exception(f"Error al crear video con efecto Ken Burns: {e.stderr}")
def combine_video_and_audio(video_path, audio_path, output_path):
command = ["ffmpeg", "-y", "-i", video_path, "-i", audio_path, "-c:v", "copy", "-c:a", "aac", "-map", "0:v:0", "-map", "1:a:0", "-shortest", output_path]
try:
subprocess.run(command, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
raise Exception(f"Error al combinar video y audio: {e.stderr}")
def generate_tts_only(news_text_input):
if not news_text_input:
return "Por favor, escribe una noticia para generar el audio.", None
try:
audio_file = text_to_speech(news_text_input, "audio_temp_preview.mp3")
return "Audio generado con éxito.", audio_file
except Exception as e:
return f"Ocurrió un error al generar solo el audio: {e}", None
def create_news_video_app(news_text_input, image_files, video_ratio, input_audio_file):
processed_image_folder = "temp_processed_images"
final_output_video_path = "video_noticia_final.mp4"
temp_video_no_audio_path = "video_sin_audio.mp4"
temp_audio_file = "audio_para_video.mp3"
if os.path.exists(processed_image_folder): shutil.rmtree(processed_image_folder)
os.makedirs(processed_image_folder)
try:
if not image_files: raise ValueError("Por favor, sube al menos una imagen.")
if isinstance(input_audio_file, str) and os.path.exists(input_audio_file) and os.path.getsize(input_audio_file) > 0:
shutil.copy(input_audio_file, temp_audio_file)
else:
if not news_text_input:
raise ValueError("Escribe una noticia para generar el audio, ya que no se proporcionó una vista previa válida.")
text_to_speech(news_text_input, temp_audio_file)
audio_duration = get_audio_duration(temp_audio_file)
if audio_duration == 0: raise ValueError("La duración del audio es cero.")
target_width, target_height = (720, 1280) if video_ratio == "9:16" else (1280, 720)
processed_images_paths = [process_image(f.name, target_width, target_height, processed_image_folder, i) for i, f in enumerate(image_files)]
processed_images_paths = [p for p in processed_images_paths if p]
if not processed_images_paths: raise ValueError("No se pudieron procesar las imágenes.")
create_video_with_ken_burns(processed_images_paths, audio_duration, 30, (target_width, target_height), temp_video_no_audio_path)
combine_video_and_audio(temp_video_no_audio_path, temp_audio_file, final_output_video_path)
return "Video generado con éxito.", final_output_video_path
except Exception as e:
return f"Ocurrió un error: {e}", None
finally:
if os.path.exists(processed_image_folder): shutil.rmtree(processed_image_folder)
if os.path.exists(temp_video_no_audio_path): os.remove(temp_video_no_audio_path)
if os.path.exists(temp_audio_file): os.remove(temp_audio_file)
if os.path.exists("audio_temp_preview.mp3"): os.remove("audio_temp_preview.mp3")
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# � Creador de Videos de Noticias")
with gr.Row():
with gr.Column(scale=2):
news_input = gr.Textbox(label="1. Escribe tu noticia aquí", lines=5)
image_upload = gr.File(label="2. Sube tus imágenes", file_count="multiple", type="filepath", file_types=[".jpg", ".jpeg", ".png"])
video_ratio_dropdown = gr.Dropdown(label="3. Elige el Formato del Video", choices=["16:9", "9:16"], value="9:16", interactive=True)
with gr.Accordion("Opciones de Audio (Opcional)", open=False):
generate_audio_button = gr.Button("Generar Solo Audio (Vista Previa)")
audio_status_message = gr.Textbox(label="Estado del Audio", interactive=False)
audio_output_preview = gr.Audio(label="Audio de Noticia (Vista Previa)", interactive=False)
generate_video_button = gr.Button("🎬 Generar Video Completo", variant="primary")
with gr.Column(scale=3):
output_message = gr.Textbox(label="Estado del Proceso", interactive=False)
video_output = gr.Video(label="Video de la Noticia Generado")
generate_audio_button.click(
fn=generate_tts_only,
inputs=[news_input],
outputs=[audio_status_message, audio_output_preview]
)
generate_video_button.click(
fn=create_news_video_app,
inputs=[news_input, image_upload, video_ratio_dropdown, audio_output_preview],
outputs=[output_message, video_output]
)
demo.launch() |