Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -1,113 +1,115 @@
|
|
1 |
-
import
|
2 |
-
import asyncio
|
3 |
-
import edge_tts
|
4 |
import requests
|
|
|
5 |
import tempfile
|
6 |
-
import
|
7 |
from datetime import datetime
|
|
|
|
|
8 |
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip
|
|
|
9 |
|
10 |
-
|
11 |
-
|
12 |
-
VOICE_NAMES = [f"{v['Name']} ({v['Gender']})" for v in VOCES]
|
13 |
|
14 |
-
|
15 |
-
|
16 |
-
# Aquí deberías integrar tu generador real, por ahora solo repetimos prompt
|
17 |
-
return f"Este es un texto generado para el tema: {prompt}"
|
18 |
-
|
19 |
-
# Simula función para buscar videos (debes conectar con Pexels u otra API)
|
20 |
-
def buscar_videos(prompt):
|
21 |
-
# Retorna lista simulada con links a videos (debes poner tu API real)
|
22 |
-
return [
|
23 |
-
{
|
24 |
-
"video_files": [{"link": "https://filesamples.com/samples/video/mp4/sample_640x360.mp4"}],
|
25 |
-
"duration": 10
|
26 |
-
},
|
27 |
-
{
|
28 |
-
"video_files": [{"link": "https://filesamples.com/samples/video/mp4/sample_640x360.mp4"}],
|
29 |
-
"duration": 10
|
30 |
-
}
|
31 |
-
]
|
32 |
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
# Generar audio TTS
|
39 |
-
archivo_audio = "audio.mp3"
|
40 |
-
await edge_tts.Communicate(texto, voz_shortname).save(archivo_audio)
|
41 |
-
audio_clip = AudioFileClip(archivo_audio)
|
42 |
-
duracion_audio = audio_clip.duration
|
43 |
-
|
44 |
-
# Cargar música si se pasa
|
45 |
-
if musica_path:
|
46 |
-
musica_clip = AudioFileClip(musica_path).volumex(0.2) # Volumen bajo para música
|
47 |
-
musica_clip = musica_clip.subclip(0, duracion_audio)
|
48 |
-
audio_final = CompositeAudioClip([musica_clip, audio_clip])
|
49 |
-
else:
|
50 |
-
audio_final = audio_clip
|
51 |
-
|
52 |
-
# Descargar y preparar videos
|
53 |
-
videos = buscar_videos(prompt)
|
54 |
-
if not videos:
|
55 |
-
return None
|
56 |
-
|
57 |
-
clips = []
|
58 |
-
for v in videos[:3]:
|
59 |
-
video_url = v['video_files'][0]['link']
|
60 |
-
response = requests.get(video_url, stream=True)
|
61 |
-
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
62 |
-
for chunk in response.iter_content(chunk_size=1024*1024):
|
63 |
-
temp_file.write(chunk)
|
64 |
-
temp_file.close()
|
65 |
-
|
66 |
-
clip = VideoFileClip(temp_file.name).subclip(0, min(duracion_audio/len(videos), v['duration']))
|
67 |
-
clips.append(clip)
|
68 |
-
|
69 |
-
video_final = concatenate_videoclips(clips)
|
70 |
-
video_final = video_final.set_audio(audio_final)
|
71 |
-
|
72 |
-
output_filename = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
|
73 |
-
video_final.write_videofile(output_filename, codec="libx264", audio_codec="aac", fps=24)
|
74 |
-
|
75 |
-
# Limpieza
|
76 |
-
audio_clip.close()
|
77 |
-
if musica_path:
|
78 |
-
musica_clip.close()
|
79 |
-
for c in clips:
|
80 |
-
c.close()
|
81 |
-
os.remove(c.filename)
|
82 |
-
os.remove(archivo_audio)
|
83 |
-
|
84 |
-
return output_filename
|
85 |
|
86 |
-
|
87 |
-
|
|
|
88 |
|
89 |
-
def
|
90 |
try:
|
91 |
-
|
92 |
-
except
|
93 |
-
|
|
|
94 |
|
95 |
-
|
96 |
-
|
97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
|
109 |
-
|
|
|
|
|
|
|
|
|
|
|
110 |
|
|
|
111 |
|
112 |
if __name__ == "__main__":
|
113 |
-
|
|
|
1 |
+
import os
|
|
|
|
|
2 |
import requests
|
3 |
+
import asyncio
|
4 |
import tempfile
|
5 |
+
import logging
|
6 |
from datetime import datetime
|
7 |
+
|
8 |
+
import gradio as gr
|
9 |
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip
|
10 |
+
import edge_tts
|
11 |
|
12 |
+
from transformers import pipeline
|
13 |
+
import torch
|
|
|
14 |
|
15 |
+
logging.basicConfig(level=logging.INFO)
|
16 |
+
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
+
# --- CONFIG ---
|
19 |
+
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
|
20 |
+
# Modelo de texto en español
|
21 |
+
MODEL_NAME = "DeepESP/gpt2-spanish"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
+
# Inicializar generador de texto
|
24 |
+
device = 0 if torch.cuda.is_available() else -1
|
25 |
+
generator = pipeline("text-generation", model=MODEL_NAME, device=device)
|
26 |
|
27 |
+
async def listar_voces():
|
28 |
try:
|
29 |
+
return await edge_tts.list_voices()
|
30 |
+
except Exception as e:
|
31 |
+
logger.error(f"Error obteniendo voces: {e}")
|
32 |
+
return [{'ShortName': 'es-ES-ElviraNeural', 'Name': 'Elvira', 'Gender': 'Female'}]
|
33 |
|
34 |
+
VOCES = asyncio.run(listar_voces())
|
35 |
+
VOICE_NAMES = [f"{v['Name']} ({v['Gender']})" for v in VOCES]
|
36 |
|
37 |
+
def generar_guion(prompt):
|
38 |
+
prompt_text = f"Escribe un guion profesional para un vídeo de YouTube sobre '{prompt}':\n"
|
39 |
+
try:
|
40 |
+
out = generator(prompt_text, max_new_tokens=300, temperature=0.7, truncate=True, num_return_sequences=1)
|
41 |
+
texto = out[0]['generated_text']
|
42 |
+
return texto
|
43 |
+
except Exception as e:
|
44 |
+
logger.error(f"Error generando guion: {e}")
|
45 |
+
return f"Error generando guion: {e}"
|
46 |
|
47 |
+
def buscar_videos(prompt, max_videos=3):
|
48 |
+
try:
|
49 |
+
url = f"https://api.pexels.com/videos/search?query={prompt}&per_page={max_videos}"
|
50 |
+
resp = requests.get(url, headers={"Authorization": PEXELS_API_KEY}, timeout=10)
|
51 |
+
return resp.json().get("videos", [])
|
52 |
+
except Exception as e:
|
53 |
+
logger.error(f"Error en Pexels: {e}")
|
54 |
+
return []
|
55 |
+
|
56 |
+
async def crear_video(prompt, voz_index, musica_path=None):
|
57 |
+
texto = generar_guion(prompt)
|
58 |
+
voz_short = VOCES[voz_index]['ShortName']
|
59 |
+
audio_file = "tts.mp3"
|
60 |
+
await edge_tts.Communicate(texto, voz_short).save(audio_file)
|
61 |
+
tts_audio = AudioFileClip(audio_file)
|
62 |
+
dur = tts_audio.duration
|
63 |
+
|
64 |
+
# Música opcional
|
65 |
+
if musica_path:
|
66 |
+
music = AudioFileClip(musica_path).volumex(0.2).subclip(0, dur)
|
67 |
+
audio_comp = CompositeAudioClip([music, tts_audio])
|
68 |
+
else:
|
69 |
+
audio_comp = tts_audio
|
70 |
+
|
71 |
+
videos = buscar_videos(prompt)
|
72 |
+
if not videos:
|
73 |
+
return None
|
74 |
+
|
75 |
+
clips = []
|
76 |
+
for v in videos:
|
77 |
+
url = v['video_files'][0]['link']
|
78 |
+
r = requests.get(url, stream=True, timeout=15)
|
79 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
80 |
+
[tmp.write(c) for c in r.iter_content(1024*1024)]
|
81 |
+
tmp.close()
|
82 |
+
clip = VideoFileClip(tmp.name).subclip(0, min(dur/len(videos), v['duration']))
|
83 |
+
clips.append(clip)
|
84 |
+
|
85 |
+
final = concatenate_videoclips(clips).set_audio(audio_comp)
|
86 |
+
out_name = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
|
87 |
+
final.write_videofile(out_name, codec="libx264", audio_codec="aac", fps=24)
|
88 |
+
|
89 |
+
# Cleanup
|
90 |
+
tts_audio.close()
|
91 |
+
for c in clips:
|
92 |
+
c.close()
|
93 |
+
os.remove(c.filename)
|
94 |
+
os.remove(audio_file)
|
95 |
+
|
96 |
+
return out_name
|
97 |
+
|
98 |
+
def run_crear(prompt, voz_name, muz_file):
|
99 |
+
try:
|
100 |
+
idx = VOICE_NAMES.index(voz_name)
|
101 |
+
except:
|
102 |
+
idx = 0
|
103 |
+
return asyncio.run(crear_video(prompt, idx, muz_file.name if muz_file else None))
|
104 |
|
105 |
+
with gr.Blocks() as app:
|
106 |
+
tema = gr.Textbox(label="Tema para guion y video")
|
107 |
+
voz = gr.Dropdown(choices=VOICE_NAMES, value=VOICE_NAMES[0], label="Voz TTS")
|
108 |
+
muz = gr.File(label="Música fondo opcional (mp3/wav)", file_types=[".mp3", ".wav"])
|
109 |
+
btn = gr.Button("Crear video")
|
110 |
+
vid_out = gr.Video(label="Video generado")
|
111 |
|
112 |
+
btn.click(fn=run_crear, inputs=[tema, voz, muz], outputs=vid_out)
|
113 |
|
114 |
if __name__ == "__main__":
|
115 |
+
app.launch()
|