File size: 8,605 Bytes
43fcbe8
c9d2e08
43fcbe8
 
ca0a564
c9d2e08
 
43fcbe8
c9d2e08
 
 
dd712f9
 
 
 
 
43fcbe8
46aa7c6
57db4ae
dd712f9
43fcbe8
c9d2e08
 
dd712f9
c9d2e08
 
 
 
dd712f9
c9d2e08
 
dd712f9
 
c9d2e08
dd712f9
 
c9d2e08
dd712f9
c9d2e08
 
dd712f9
 
 
 
 
 
 
 
 
 
 
 
 
 
c9d2e08
 
dd712f9
57db4ae
c9d2e08
 
 
dd712f9
 
 
 
c9d2e08
57db4ae
c9d2e08
 
dd712f9
c9d2e08
dd712f9
c9d2e08
 
dd712f9
c9d2e08
43fcbe8
c9d2e08
 
dd712f9
 
c9d2e08
af3b33e
dd712f9
c9d2e08
 
dd712f9
43fcbe8
ca0a564
dd712f9
c9d2e08
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd712f9
 
 
 
 
 
 
 
 
c9d2e08
 
 
dd712f9
c9d2e08
 
 
 
dd712f9
c9d2e08
 
 
 
dd712f9
43fcbe8
 
c9d2e08
 
dd712f9
c9d2e08
 
 
 
dd712f9
c9d2e08
 
 
 
dd712f9
c9d2e08
 
 
 
 
dd712f9
 
 
c9d2e08
 
dd712f9
c9d2e08
 
 
 
 
dd712f9
c9d2e08
 
 
 
 
 
 
 
dd712f9
c9d2e08
 
dd712f9
c9d2e08
 
 
 
dd712f9
c9d2e08
 
ca0a564
c9d2e08
dd712f9
ca0a564
c9d2e08
15175ef
dd712f9
c9d2e08
43fcbe8
c9d2e08
dd712f9
c9d2e08
 
 
 
46aa7c6
 
c9d2e08
 
 
 
dd712f9
c9d2e08
 
 
 
 
 
 
 
 
 
 
 
43fcbe8
af3b33e
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import os
import random
import requests
import gradio as gr
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeVideoClip
from moviepy.audio.fx.all import audio_loop
import edge_tts
import asyncio
from datetime import datetime
from pathlib import Path
from transformers import pipeline
import logging

# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

# Pexels API key from environment variable
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
logger.info("Loaded PEXELS_API_KEY from environment")

# Ensure asyncio works with Gradio
def run_async(coro):
    logger.info("Running async coroutine")
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    result = loop.run_until_complete(coro)
    loop.close()
    logger.info("Async coroutine completed")
    return result

# Load lightweight text generation model for Spanish
logger.info("Loading text generation model: facebook/mbart-large-50")
try:
    generator = pipeline("text-generation", model="facebook/mbart-large-50", device="cpu")
    logger.info("Model loaded successfully")
except Exception as e:
    logger.error(f"Error loading model: {e}")
    generator = None

# List available Spanish voices for Edge TTS
SPANISH_VOICES = [
    "es-MX-DaliaNeural",
    "es-MX-JorgeNeural",
    "es-MX-CecilioNeural",
    "es-MX-BeatrizNeural",
    "es-MX-CandelaNeural",
    "es-MX-CarlosNeural",
    "es-MX-LarissaNeural",
    "es-MX-ManuelNeural",
    "es-MX-MarinaNeural",
    "es-MX-NuriaNeural"
]

# Fetch videos from Pexels
def fetch_pexels_videos(query, num_videos=5):
    logger.info(f"Fetching {num_videos} videos from Pexels with query: {query}")
    headers = {"Authorization": PEXELS_API_KEY}
    url = f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos}"
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        videos = [video["video_files"][0]["link"] for video in response.json()["videos"]]
        logger.info(f"Fetched {len(videos)} videos from Pexels")
        return videos
    logger.error("Failed to fetch videos from Pexels")
    return []

# Generate script using local model or custom text
def generate_script(prompt, custom_text=None):
    logger.info("Generating script")
    if custom_text:
        logger.info("Using custom text provided by user")
        return custom_text.strip()
    if not prompt:
        logger.error("No prompt or custom text provided")
        return "Error: Debes proporcionar un prompt o un guion personalizado."
    
    # Generate script with local model
    if generator:
        input_text = f"Genera un guion para un video sobre '{prompt}'. Crea una lista numerada con descripciones breves (máximo 20 palabras por ítem) para un top 10 relacionado con el tema en español."
        logger.info(f"Generating script with prompt: {prompt}")
        try:
            result = generator(input_text, max_length=300, num_return_sequences=1, do_sample=True, truncation=True)[0]['generated_text']
            logger.info("Script generated successfully")
            return result.strip()
        except Exception as e:
            logger.error(f"Error generating script: {e}")
    
    # Fallback mock response
    logger.info("Using fallback mock response")
    if "recetas" in prompt.lower():
        return """
        1. Tacos al pastor: Jugosa carne marinada con piña.
        2. Lasagna: Capas de pasta, carne y queso fundido.
        3. Sushi: Arroz y pescado fresco en rollos delicados.
        4. Pizza casera: Masa crujiente con tus ingredientes favoritos.
        5. Paella: Arroz con mariscos y azafrán.
        6. Ceviche: Pescado fresco marinado en limón.
        7. Ramen: Caldo rico con fideos y cerdo.
        8. Tiramisú: Postre cremoso con café y mascarpone.
        9. Enchiladas: Tortillas rellenas con salsa picante.
        10. Curry: Especias intensas con carne o vegetales.
        """
    return f"Top 10 sobre {prompt}: No se pudo generar un guion específico."

# Generate voice using Edge TTS
async def generate_voice(text, output_file="output.mp3"):
    logger.info(f"Generating voice with Edge TTS using voice: es-MX-DaliaNeural")
    try:
        communicate = edge_tts.Communicate(text, voice="es-MX-DaliaNeural")
        await communicate.save(output_file)
        logger.info(f"Voice generated and saved to {output_file}")
        return output_file
    except Exception as e:
        logger.error(f"Error generating voice: {e}")
        return None

# Download and trim video
def download_and_trim_video(url, duration, output_path):
    logger.info(f"Downloading video from {url}")
    response = requests.get(url, stream=True)
    with open("temp_video.mp4", "wb") as f:
        for chunk in response.iter_content(chunk_size=1024):
            f.write(chunk)
    logger.info("Trimming video")
    clip = VideoFileClip("temp_video.mp4").subclip(0, min(duration, VideoFileClip("temp_video.mp4").duration))
    clip.write_videofile(output_path, codec="libx264", audio_codec="aac")
    clip.close()
    os.remove("temp_video.mp4")
    logger.info(f"Video trimmed and saved to {output_path}")
    return output_path

# Main video creation function
def create_video(prompt, custom_text, music_file):
    logger.info("Starting video creation process")
    output_dir = "output_videos"
    os.makedirs(output_dir, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_video = f"{output_dir}/video_{timestamp}.mp4"
    logger.info(f"Output video will be saved to {output_video}")

    # Generate or use provided script
    script = generate_script(prompt, custom_text)
    if "Error" in script:
        logger.error(script)
        return script

    # Generate voice
    voice_file = "temp_audio.mp3"
    run_async(generate_voice(script, voice_file))
    if not os.path.exists(voice_file):
        logger.error("Voice generation failed")
        return "Error: No se pudo generar la voz."
    audio = AudioFileClip(voice_file)
    video_duration = audio.duration
    logger.info(f"Audio duration: {video_duration} seconds")

    # Fetch Pexels videos
    query = prompt.split()[0] if prompt else "generic"
    video_urls = fetch_pexels_videos(query, num_videos=5)
    if not video_urls:
        logger.error("No videos found on Pexels")
        return "Error: No se encontraron videos en Pexels."

    # Download and trim videos
    clips = []
    for i, url in enumerate(video_urls):
        clip_path = f"temp_clip_{i}.mp4"
        download_and_trim_video(url, video_duration / len(video_urls), clip_path)
        clips.append(VideoFileClip(clip_path))
        logger.info(f"Processed video clip {i+1}/{len(video_urls)}")

    # Concatenate video clips
    logger.info("Concatenating video clips")
    final_clip = concatenate_videoclips(clips, method="compose").set_duration(video_duration)

    # Add looped music
    if music_file:
        logger.info("Adding user-uploaded music")
        music = AudioFileClip(music_file.name)
        music = audio_loop(music, duration=video_duration)
        final_clip = final_clip.set_audio(music.set_duration(video_duration))
    else:
        logger.info("Using generated voice as audio")
        final_clip = final_clip.set_audio(audio)

    # Write final video
    logger.info(f"Writing final video to {output_video}")
    final_clip.write_videofile(output_video, codec="libx264", audio_codec="aac", fps=24)
    
    # Clean up
    logger.info("Cleaning up temporary files")
    for clip in clips:
        clip.close()
    audio.close()
    if music_file:
        music.close()
    final_clip.close()
    os.remove(voice_file)
    for i in range(len(video_urls)):
        os.remove(f"temp_clip_{i}.mp4")

    logger.info("Video creation completed")
    return output_video

# Gradio interface
with gr.Blocks() as demo:
    gr.Markdown("# Generador de Videos")
    gr.Markdown("Ingresa un prompt (ej., 'Top 10 recetas más ricas') o un guion personalizado. Sube música opcional (MP3).")
    prompt = gr.Textbox(label="Prompt", placeholder="Ejemplo: Top 10 recetas más ricas")
    custom_text = gr.Textbox(label="Guion Personalizado", placeholder="Ingresa tu propio guion aquí (opcional)")
    music_file = gr.File(label="Subir Música (MP3, opcional)", file_types=[".mp3"])
    submit = gr.Button("Generar Video")
    output = gr.Video(label="Video Generado")
    submit.click(fn=create_video, inputs=[prompt, custom_text, music_file], outputs=output)

demo.launch(share=True)