gnosticdev commited on
Commit
57db4ae
·
verified ·
1 Parent(s): a483f49

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -103
app.py CHANGED
@@ -3,140 +3,123 @@ import requests
3
  import edge_tts
4
  import gradio as gr
5
  from moviepy.editor import *
6
- from moviepy.video.fx.all import resize, scroll
7
  from PIL import Image
8
  import io
9
  import asyncio
10
  import json
 
11
 
12
- # 1. Descargar imágenes/videos de stock (Pexels)
13
- def get_stock_media(query, is_video=False):
14
- API_KEY = os.getenv("PEXELS_API_KEY") # Configurar en HF Secrets
15
- if not API_KEY:
16
- raise ValueError("¡Falta la API Key de Pexels! Añádela en los Secrets de Hugging Face.")
17
-
18
- url = f"https://api.pexels.com/v1/{'videos' if is_video else 'photos'}/search?query={query}&per_page=1"
19
- headers = {"Authorization": API_KEY}
20
- response = requests.get(url, headers=headers).json()
21
-
22
- if is_video:
23
- video_url = response["videos"][0]["video_files"][0]["link"]
24
- return requests.get(video_url).content
25
- else:
26
- image_url = response["photos"][0]["src"]["large"]
27
- return Image.open(io.BytesIO(requests.get(image_url).content))
28
 
29
- # 2. Generar voz con Edge TTS
30
- async def generate_voice(text, voice="es-ES-AlvaroNeural", output_path="voice.mp3"):
31
- communicate = edge_tts.Communicate(text=text, voice=voice)
32
- await communicate.save(output_path)
33
-
34
- # 3. Añadir música de fondo (20% volumen, loop automático)
35
- def add_background_music(audio_clip, music_path=None, volume=0.2):
36
- if not music_path:
37
- return audio_clip
38
- music = AudioFileClip(music_path).volumex(volume)
39
- if music.duration < audio_clip.duration:
40
- music = music.loop(duration=audio_clip.duration)
41
- return CompositeAudioClip([audio_clip, music.set_start(0)])
 
 
 
42
 
43
- # 4. Efectos de movimiento/zoom para imágenes
44
- def apply_effects(clip, zoom_factor=1.05, effect_duration=2):
45
- return clip.resize(zoom_factor).set_position('center').fx(scroll, h=50, w=50)
 
 
 
 
46
 
47
- # 5. Subtítulos dinámicos (aparecen progresivamente)
48
- def generate_subtitles(text, duration, fontsize=30, color="white", stroke_color="black"):
49
- words = text.split()
50
- word_duration = duration / max(len(words), 1) # Evitar división por cero
51
- clips = []
52
- for i, word in enumerate(words):
53
- txt_clip = TextClip(
54
- " ".join(words[:i+1]),
55
- fontsize=fontsize,
56
- color=color,
57
- stroke_color=stroke_color,
58
- font="Arial-Bold",
59
- size=(None, None),
60
- method="caption"
61
- ).set_start(i * word_duration).set_duration(word_duration)
62
- clips.append(txt_clip.set_position(("center", "bottom")))
63
- return concatenate_videoclips(clips)
64
 
65
- # 6. Función principal
66
- async def generate_video(script_json, voice_model, music_file=None):
67
  try:
68
  script = json.loads(script_json)
69
  except json.JSONDecodeError:
70
- raise gr.Error("¡Formato de script inválido! Usa JSON como en el ejemplo.")
71
-
72
  clips = []
73
  for i, scene in enumerate(script):
74
  img = get_stock_media(scene["prompt"])
75
- img_path = f"scene_{i}.jpg"
76
- img.save(img_path)
77
 
78
- voice_path = f"voice_{i}.mp3"
79
- await generate_voice(scene["text"], voice_model, voice_path)
80
 
81
- audio = AudioFileClip(voice_path)
82
- clip = ImageClip(img_path).set_duration(audio.duration)
83
- clip = apply_effects(clip)
 
 
 
 
 
 
84
 
85
- subtitles = generate_subtitles(scene["text"], audio.duration)
86
- final_clip = CompositeVideoClip([clip, subtitles]).set_audio(audio)
87
- clips.append(final_clip)
88
 
89
  final_video = concatenate_videoclips(clips)
90
  if music_file:
91
- final_video.audio = add_background_music(final_video.audio, music_file)
 
92
 
93
  output_path = "final_video.mp4"
94
- final_video.write_videofile(output_path, fps=24, codec="libx264", threads=4)
95
  return output_path
96
 
97
- # 7. Interfaz Gradio
98
- def ui(script_json, voice_model, music_file=None):
99
- loop = asyncio.new_event_loop()
100
- asyncio.set_event_loop(loop)
101
- try:
102
- output_video = loop.run_until_complete(generate_video(script_json, voice_model, music_file))
103
- except Exception as e:
104
- raise gr.Error(f"Error: {str(e)}")
105
- finally:
106
- loop.close()
107
- return output_video
108
-
109
- # Voces Edge TTS (puedes añadir más)
110
- voices = list(edge_tts.list_voices())
111
-
112
- with gr.Blocks(title="Generador de Videos con IA") as demo:
113
- gr.Markdown("## 🎥 Generador de Videos con IA (Gratis)")
114
- with gr.Row():
115
  script_input = gr.Textbox(
116
- label="Script (JSON)",
117
- placeholder='[{"prompt": "paisaje", "text": "Texto aquí..."}]',
118
- lines=5
119
  )
120
- with gr.Row():
121
- voice_dropdown = gr.Dropdown(choices=voices, label="Voz", value="es-ES-AlvaroNeural")
122
  music_upload = gr.File(label="Música de fondo (opcional)", type="filepath")
123
- generate_btn = gr.Button("Generar Video")
124
- output_video = gr.Video(label="Resultado", format="mp4")
 
 
 
 
 
 
125
 
126
- gr.Examples(
127
- examples=[[
128
- '[{"prompt": "ciudad futurista", "text": "Bienvenidos al futuro."}]',
129
- "es-ES-AlvaroNeural",
130
- None
131
- ]],
132
- inputs=[script_input, voice_dropdown, music_upload],
133
  outputs=output_video
134
  )
135
 
136
- generate_btn.click(
137
- fn=ui,
138
- inputs=[script_input, voice_dropdown, music_upload],
139
- outputs=output_video,
140
  )
141
 
142
- demo.launch(debug=True)
 
3
  import edge_tts
4
  import gradio as gr
5
  from moviepy.editor import *
 
6
  from PIL import Image
7
  import io
8
  import asyncio
9
  import json
10
+ from openai import OpenAI
11
 
12
+ # Configura APIs (gratis)
13
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Para GPT-3.5-turbo
14
+ PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ # 1. Generar guion automático con IA (si el usuario no proporciona uno)
17
+ async def generate_script(topic):
18
+ prompt = f"""
19
+ Genera un guion para un video de YouTube sobre '{topic}'.
20
+ Formato JSON ejemplo:
21
+ [
22
+ {{"prompt": "imagen de ejemplo", "text": "narración correspondiente"}},
23
+ ...
24
+ ]
25
+ """
26
+ response = client.chat.completions.create(
27
+ model="gpt-3.5-turbo",
28
+ messages=[{"role": "user", "content": prompt}],
29
+ temperature=0.7
30
+ )
31
+ return response.choices[0].message.content
32
 
33
+ # 2. Descargar imágenes de Pexels/Unsplash
34
+ def get_stock_media(query):
35
+ url = f"https://api.pexels.com/v1/photos/search?query={query}&per_page=1"
36
+ headers = {"Authorization": PEXELS_API_KEY}
37
+ response = requests.get(url, headers=headers).json()
38
+ image_url = response["photos"][0]["src"]["large"]
39
+ return Image.open(io.BytesIO(requests.get(image_url).content))
40
 
41
+ # 3. Generar voz con Edge TTS
42
+ async def generate_voice(text, voice="es-ES-AlvaroNeural"):
43
+ communicate = edge_tts.Communicate(text=text, voice=voice)
44
+ await communicate.save("voice.mp3")
45
+ return AudioFileClip("voice.mp3")
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ # 4. Crear video final
48
+ async def create_video(script_json, voice_model, music_file=None):
49
  try:
50
  script = json.loads(script_json)
51
  except json.JSONDecodeError:
52
+ raise gr.Error("¡Error en el formato del guion! Usa JSON válido.")
53
+
54
  clips = []
55
  for i, scene in enumerate(script):
56
  img = get_stock_media(scene["prompt"])
57
+ img.save(f"scene_{i}.jpg")
 
58
 
59
+ audio = await generate_voice(scene["text"], voice_model)
60
+ clip = ImageClip(f"scene_{i}.jpg").set_duration(audio.duration)
61
 
62
+ # Subtítulos dinámicos
63
+ text_clip = TextClip(
64
+ scene["text"],
65
+ fontsize=30,
66
+ color="white",
67
+ stroke_color="black",
68
+ size=(clip.w * 0.9, None),
69
+ method="caption"
70
+ ).set_position(("center", "bottom")).set_duration(audio.duration)
71
 
72
+ clips.append(CompositeVideoClip([clip, text_clip]).set_audio(audio))
 
 
73
 
74
  final_video = concatenate_videoclips(clips)
75
  if music_file:
76
+ music = AudioFileClip(music_file).volumex(0.2)
77
+ final_video.audio = CompositeAudioClip([final_video.audio, music.set_start(0)])
78
 
79
  output_path = "final_video.mp4"
80
+ final_video.write_videofile(output_path, fps=24, codec="libx264")
81
  return output_path
82
 
83
+ # 5. Interfaz Gradio (2 modos: automático o manual)
84
+ with gr.Blocks() as demo:
85
+ gr.Markdown("## 🎥 Generador de Videos con IA (Modo Automático o Manual)")
86
+
87
+ with gr.Tab("Modo Automático"):
88
+ topic_input = gr.Textbox(label="Tema del video (ej: 'Top 10 misterios del mundo')")
89
+ auto_voice = gr.Dropdown(label="Voz", choices=["es-ES-AlvaroNeural", "en-US-JennyNeural"])
90
+ generate_auto_btn = gr.Button("Generar Guion y Video")
91
+
92
+ with gr.Tab("Modo Manual"):
 
 
 
 
 
 
 
 
93
  script_input = gr.Textbox(
94
+ label="Pega tu guion (JSON)",
95
+ placeholder='[{"prompt": "ciudad futurista", "text": "Bienvenidos al futuro..."}]',
96
+ lines=10
97
  )
98
+ manual_voice = gr.Dropdown(label="Voz", choices=["es-ES-AlvaroNeural", "en-US-JennyNeural"])
 
99
  music_upload = gr.File(label="Música de fondo (opcional)", type="filepath")
100
+ generate_manual_btn = gr.Button("Generar Video")
101
+
102
+ output_video = gr.Video(label="Video Generado", format="mp4")
103
+
104
+ # Modo Automático: Generar guion + video
105
+ async def auto_mode(topic, voice):
106
+ script = await generate_script(topic)
107
+ return await create_video(script, voice)
108
 
109
+ # Modo Manual: Usar guion existente
110
+ async def manual_mode(script, voice, music):
111
+ return await create_video(script, voice, music)
112
+
113
+ generate_auto_btn.click(
114
+ fn=auto_mode,
115
+ inputs=[topic_input, auto_voice],
116
  outputs=output_video
117
  )
118
 
119
+ generate_manual_btn.click(
120
+ fn=manual_mode,
121
+ inputs=[script_input, manual_voice, music_upload],
122
+ outputs=output_video
123
  )
124
 
125
+ demo.launch()