gnosticdev commited on
Commit
c9d2e08
·
verified ·
1 Parent(s): 323e604

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -109
app.py CHANGED
@@ -1,125 +1,176 @@
1
  import os
 
2
  import requests
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()
 
1
  import os
2
+ import random
3
  import requests
 
4
  import gradio as gr
5
+ from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, TextClip, CompositeVideoClip
6
+ from moviepy.audio.fx.all import audio_loop
7
+ import edge_tts
8
  import asyncio
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from transformers import pipeline
12
 
13
+ # Pexels API key from Hugging Face Space environment variable
 
14
  PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
15
 
16
+ # Ensure asyncio works with Gradio
17
+ def run_async(coro):
18
+ loop = asyncio.new_event_loop()
19
+ asyncio.set_event_loop(loop)
20
+ result = loop.run_until_complete(coro)
21
+ loop.close()
22
+ return result
23
+
24
+ # Load local text generation model (adjust based on Hugging Face Spaces resources)
25
+ try:
26
+ generator = pipeline("text-generation", model="mistralai/Mistral-7B-Instruct-v0.2", device="cpu")
27
+ except Exception as e:
28
+ print(f"Error loading model: {e}")
29
+ generator = None
30
+
31
+ # Fetch videos from Pexels
32
+ def fetch_pexels_videos(query, num_videos=5):
 
 
 
33
  headers = {"Authorization": PEXELS_API_KEY}
34
+ url = f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos}"
35
+ response = requests.get(url, headers=headers)
36
+ if response.status_code == 200:
37
+ return [video["video_files"][0]["link"] for video in response.json()["videos"]]
38
+ return []
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ # Generate script using local model or custom text
41
+ def generate_script(prompt, custom_text=None):
42
+ if custom_text:
43
+ return custom_text.strip()
44
+ if not prompt:
45
+ return "Error: Debes proporcionar un prompt o un guion personalizado."
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ # Generate script with local model
48
+ if generator:
49
+ 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."
50
+ try:
51
+ result = generator(input_text, max_length=300, num_return_sequences=1, do_sample=True)[0]['generated_text']
52
+ return result.strip()
53
+ except Exception as e:
54
+ print(f"Error generating script: {e}")
55
 
56
+ # Fallback mock response if model fails
57
+ if "recetas" in prompt.lower():
58
+ return """
59
+ 1. Tacos al pastor: Jugosa carne marinada con piña.
60
+ 2. Lasagna: Capas de pasta, carne y queso fundido.
61
+ 3. Sushi: Arroz y pescado fresco en rollos delicados.
62
+ 4. Pizza casera: Masa crujiente con tus ingredientes favoritos.
63
+ 5. Paella: Arroz con mariscos y azafrán.
64
+ 6. Ceviche: Pescado fresco marinado en limón.
65
+ 7. Ramen: Caldo rico con fideos y cerdo.
66
+ 8. Tiramisú: Postre cremoso con café y mascarpone.
67
+ 9. Enchiladas: Tortillas rellenas con salsa picante.
68
+ 10. Curry: Especias intensas con carne o vegetales.
69
+ """
70
+ return f"Top 10 sobre {prompt}: No se pudo generar un guion específico."
71
+
72
+ # Generate voice using Edge TTS
73
+ async def generate_voice(text, output_file="output.mp3"):
74
+ communicate = edge_tts.Communicate(text, voice="es-MX-DaliaNeural")
75
+ await communicate.save(output_file)
76
+ return output_file
77
+
78
+ # Generate subtitles
79
+ def generate_subtitles(text, duration, fps=24):
80
+ words = text.split()
81
+ avg_duration = duration / len(words)
82
+ subtitles = []
83
+ for i, word in enumerate(words):
84
+ start_time = i * avg_duration
85
+ end_time = (i + 1) * avg_duration
86
+ subtitle = TextClip(word, fontsize=30, color='white', bg_color='black', size=(1280, 100))
87
+ subtitle = subtitle.set_position(('center', 'bottom')).set_duration(avg_duration).set_fps(fps)
88
+ subtitles.append(subtitle)
89
+ return subtitles
90
+
91
+ # Download and trim video
92
+ def download_and_trim_video(url, duration, output_path):
93
+ response = requests.get(url, stream=True)
94
+ with open("temp_video.mp4", "wb") as f:
95
+ for chunk in response.iter_content(chunk_size=1024):
96
+ f.write(chunk)
97
+ clip = VideoFileClip("temp_video.mp4").subclip(0, min(duration, VideoFileClip("temp_video.mp4").duration))
98
+ clip.write_videofile(output_path, codec="libx264", audio_codec="aac")
99
+ clip.close()
100
+ os.remove("temp_video.mp4")
101
  return output_path
102
 
103
+ # Main video creation function
104
+ def create_video(prompt, custom_text, music_file):
105
+ output_dir = "output_videos"
106
+ os.makedirs(output_dir, exist_ok=True)
107
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
108
+ output_video = f"{output_dir}/video_{timestamp}.mp4"
109
+
110
+ # Generate or use provided script
111
+ script = generate_script(prompt, custom_text)
112
+ if "Error" in script:
113
+ return script
114
+
115
+ # Generate voice
116
+ voice_file = "temp_audio.mp3"
117
+ run_async(generate_voice(script, voice_file))
118
+ audio = AudioFileClip(voice_file)
119
+ video_duration = audio.duration
120
+
121
+ # Fetch Pexels videos
122
+ query = prompt.split()[0] if prompt else "generic"
123
+ video_urls = fetch_pexels_videos(query, num_videos=5)
124
+ if not video_urls:
125
+ return "Error: No se encontraron videos en Pexels."
126
+
127
+ # Download and trim videos
128
+ clips = []
129
+ for i, url in enumerate(video_urls):
130
+ clip_path = f"temp_clip_{i}.mp4"
131
+ download_and_trim_video(url, video_duration / len(video_urls), clip_path)
132
+ clips.append(VideoFileClip(clip_path))
133
+
134
+ # Concatenate video clips
135
+ final_clip = concatenate_videoclips(clips, method="compose").set_duration(video_duration)
136
+
137
+ # Add looped music
138
+ if music_file:
139
+ music = AudioFileClip(music_file.name)
140
+ music = audio_loop(music, duration=video_duration)
141
+ final_audio = final_clip.set_audio(music.set_duration(video_duration))
142
+ else:
143
+ final_audio = final_clip.set_audio(audio)
144
+
145
+ # Add subtitles
146
+ subtitles = generate_subtitles(script, video_duration)
147
+ final_clip = CompositeVideoClip([final_clip] + subtitles)
148
+
149
+ # Write final video
150
+ final_clip.write_videofile(output_video, codec="libx264", audio_codec="aac", fps=24)
151
 
152
+ # Clean up
153
+ for clip in clips:
154
+ clip.close()
155
+ audio.close()
156
+ if music_file:
157
+ music.close()
158
+ final_clip.close()
159
+ os.remove(voice_file)
160
+ for i in range(len(video_urls)):
161
+ os.remove(f"temp_clip_{i}.mp4")
162
+
163
+ return output_video
164
+
165
+ # Gradio interface
166
+ with gr.Blocks() as demo:
167
+ gr.Markdown("# Generador de Videos")
168
+ gr.Markdown("Ingresa un prompt (ej., 'Top 10 recetas más ricas') o un guion personalizado. Sube música opcional (MP3).")
169
+ prompt = gr.Textbox(label="Prompt", placeholder="Ejemplo: Top 10 recetas más ricas")
170
+ custom_text = gr.Textbox(label="Guion Personalizado", placeholder="Ingresa tu propio guion aquí (opcional)")
171
+ music_file = gr.File(label="Subir Música (MP3, opcional)", file_types=[".mp3"])
172
+ submit = gr.Button("Generar Video")
173
+ output = gr.Video(label="Video Generado")
174
+ submit.click(fn=create_video, inputs=[prompt, custom_text, music_file], outputs=output)
175
 
176
  demo.launch()