mgbam commited on
Commit
8b30fd7
·
verified ·
1 Parent(s): 2bfad86

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +20 -43
app.py CHANGED
@@ -1,27 +1,29 @@
1
  import gradio as gr
2
  import os
3
  import google.generativeai as genai
4
- import elevenlabs
 
5
  from tavily import TavilyClient
6
- import requests # For Runway API calls
7
  import subprocess
8
  import json
9
  import time
10
  import random
11
 
12
  # --- 1. CONFIGURE API KEYS FROM HUGGING FACE SECRETS ---
13
- # Ensure you have set these in your Space's settings -> secrets
14
  try:
15
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
16
- elevenlabs.set_api_key(os.environ["ELEVENLABS_API_KEY"])
17
  tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
18
  RUNWAY_API_KEY = os.environ["RUNWAY_API_KEY"]
 
 
 
 
19
  except KeyError as e:
20
  raise ValueError(f"API Key Error: Please set the {e} secret in your Hugging Face Space settings.")
21
 
22
  # --- 2. DEFINE API ENDPOINTS AND HEADERS ---
23
- # NOTE: Check the latest RunwayML API documentation for the correct endpoint. This is a common one.
24
- RUNWAY_API_URL = "https://api.runwayml.com/v1/jobs"
25
  RUNWAY_HEADERS = {
26
  "Authorization": f"Bearer {RUNWAY_API_KEY}",
27
  "Content-Type": "application/json"
@@ -29,12 +31,6 @@ RUNWAY_HEADERS = {
29
 
30
  # --- 3. THE CORE VIDEO GENERATION FUNCTION ---
31
  def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True)):
32
- """
33
- Main function to orchestrate the video generation pipeline.
34
- It takes a topic and returns the path to the final generated video.
35
- """
36
-
37
- # Use a unique ID for this job to prevent file collisions
38
  job_id = f"{int(time.time())}_{random.randint(1000, 9999)}"
39
  print(f"--- Starting New Job: {job_id} for topic: '{topic_prompt}' ---")
40
 
@@ -64,22 +60,10 @@ def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True
64
  Your output MUST be a valid JSON object with two keys:
65
  1. "narration_script": A string containing the full voiceover narration. Make it engaging and concise.
66
  2. "scene_prompts": A list of exactly 4 strings. Each string must be a highly detailed, visually rich, and cinematic prompt for a text-to-video AI like Runway Gen-2. Describe camera angles, lighting, and mood.
67
-
68
- Example JSON format:
69
- {{
70
- "narration_script": "Did you know the ocean's depths hold more history than all the world's museums combined? Let's dive in...",
71
- "scene_prompts": [
72
- "An ultra-realistic, cinematic shot of a massive blue whale gliding through deep, sun-dappled ocean water, camera tracking smoothly alongside it.",
73
- "A dramatic, slow-motion close-up of an ancient shipwreck on the seabed, covered in coral, with schools of small fish swimming through its broken hull.",
74
- "A bioluminescent jellyfish pulsing with ethereal light in the pitch-black abyss, shot with a macro lens.",
75
- "A wide, epic shot of a volcanic vent on the ocean floor erupting with dark smoke, viewed from a safe distance, creating a sense of immense power."
76
- ]
77
- }}
78
  """
79
  response = gemini_model.generate_content(prompt)
80
 
81
  try:
82
- # Clean the response text before parsing
83
  cleaned_text = response.text.strip().replace("```json", "").replace("```", "")
84
  script_data = json.loads(cleaned_text)
85
  narration = script_data['narration_script']
@@ -92,30 +76,32 @@ def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True
92
  progress(0.3, desc="🎙️ Recording voiceover with ElevenLabs...")
93
  audio_path = f"audio_{job_id}.mp3"
94
  intermediate_files.append(audio_path)
95
- audio_bytes = elevenlabs.generate(text=narration, voice="Adam", model="eleven_multilingual_v2")
 
 
 
 
 
 
96
  with open(audio_path, "wb") as f:
97
  f.write(audio_bytes)
98
  print(f"Audio file saved: {audio_path}")
99
 
100
- # STEP 4: VISUALS (Runway) - This is the most complex step
101
  video_clip_paths = []
102
  for i, scene_prompt in enumerate(scene_prompts):
103
  progress(0.4 + (i * 0.12), desc=f"🎬 Generating video scene {i+1}/{len(scene_prompts)}...")
104
-
105
- # A. Start the generation job
106
  runway_payload = {"text_prompt": scene_prompt}
107
  post_response = requests.post(RUNWAY_API_URL, headers=RUNWAY_HEADERS, json=runway_payload)
108
  if post_response.status_code != 200:
109
  raise gr.Error(f"Runway API Error (start job): {post_response.status_code} - {post_response.text}")
110
 
111
- job_details = post_response.json()
112
- task_id = job_details.get("uuid")
113
  if not task_id:
114
- raise gr.Error(f"Runway API did not return a task UUID. Response: {job_details}")
115
 
116
- # B. Poll for job completion
117
  video_url = None
118
- for _ in range(60): # Poll for up to 10 minutes (60 * 10s)
119
  get_response = requests.get(f"{RUNWAY_API_URL}/{task_id}", headers=RUNWAY_HEADERS)
120
  status_details = get_response.json()
121
  status = status_details.get("status")
@@ -130,9 +116,8 @@ def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True
130
  time.sleep(10)
131
 
132
  if not video_url:
133
- raise gr.Error(f"Runway job timed out after 10 minutes for scene {i+1}.")
134
 
135
- # C. Download the generated video
136
  clip_path = f"scene_{i+1}_{job_id}.mp4"
137
  intermediate_files.append(clip_path)
138
  video_clip_paths.append(clip_path)
@@ -145,22 +130,17 @@ def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True
145
 
146
  # STEP 5: STITCHING (FFmpeg)
147
  progress(0.9, desc="✂️ Assembling final video with FFmpeg...")
148
-
149
- # Create a file list for ffmpeg
150
  file_list_path = f"file_list_{job_id}.txt"
151
  intermediate_files.append(file_list_path)
152
  with open(file_list_path, "w") as f:
153
  for clip in video_clip_paths:
154
  f.write(f"file '{clip}'\n")
155
 
156
- # Concatenate video clips
157
  combined_video_path = f"combined_video_{job_id}.mp4"
158
  intermediate_files.append(combined_video_path)
159
  subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', file_list_path, '-c', 'copy', combined_video_path, '-y'], check=True)
160
 
161
- # Add audio to the combined video
162
  final_video_path = f"final_video_{job_id}.mp4"
163
- # We don't add this to intermediate files because we want to keep it
164
  subprocess.run([
165
  'ffmpeg', '-i', combined_video_path, '-i', audio_path, '-c:v', 'copy',
166
  '-c:a', 'aac', '-shortest', final_video_path, '-y'
@@ -171,20 +151,17 @@ def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True
171
  return final_video_path
172
 
173
  except Exception as e:
174
- # If anything goes wrong, raise a Gradio error to display it in the UI
175
  print(f"--- JOB {job_id} FAILED --- \nError: {e}")
176
  raise gr.Error(f"An error occurred: {e}")
177
 
178
  finally:
179
  # STEP 6: CLEANUP
180
- # Clean up all the temporary files we created
181
  print("Cleaning up intermediate files...")
182
  for file_path in intermediate_files:
183
  if os.path.exists(file_path):
184
  os.remove(file_path)
185
  print(f"Removed: {file_path}")
186
 
187
-
188
  # --- 4. CREATE AND LAUNCH THE GRADIO INTERFACE ---
189
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
190
  gr.Markdown(
 
1
  import gradio as gr
2
  import os
3
  import google.generativeai as genai
4
+ # --- CHANGE 1: Import the new ElevenLabs client ---
5
+ from elevenlabs.client import ElevenLabs
6
  from tavily import TavilyClient
7
+ import requests
8
  import subprocess
9
  import json
10
  import time
11
  import random
12
 
13
  # --- 1. CONFIGURE API KEYS FROM HUGGING FACE SECRETS ---
 
14
  try:
15
  genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
16
  tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
17
  RUNWAY_API_KEY = os.environ["RUNWAY_API_KEY"]
18
+
19
+ # --- CHANGE 2: Create an instance of the ElevenLabs client ---
20
+ elevenlabs_client = ElevenLabs(api_key=os.environ["ELEVENLABS_API_KEY"])
21
+
22
  except KeyError as e:
23
  raise ValueError(f"API Key Error: Please set the {e} secret in your Hugging Face Space settings.")
24
 
25
  # --- 2. DEFINE API ENDPOINTS AND HEADERS ---
26
+ RUNWAY_API_URL = "https://api.runwayml.com/v1/jobs"
 
27
  RUNWAY_HEADERS = {
28
  "Authorization": f"Bearer {RUNWAY_API_KEY}",
29
  "Content-Type": "application/json"
 
31
 
32
  # --- 3. THE CORE VIDEO GENERATION FUNCTION ---
33
  def generate_video_from_topic(topic_prompt, progress=gr.Progress(track_tqdm=True)):
 
 
 
 
 
 
34
  job_id = f"{int(time.time())}_{random.randint(1000, 9999)}"
35
  print(f"--- Starting New Job: {job_id} for topic: '{topic_prompt}' ---")
36
 
 
60
  Your output MUST be a valid JSON object with two keys:
61
  1. "narration_script": A string containing the full voiceover narration. Make it engaging and concise.
62
  2. "scene_prompts": A list of exactly 4 strings. Each string must be a highly detailed, visually rich, and cinematic prompt for a text-to-video AI like Runway Gen-2. Describe camera angles, lighting, and mood.
 
 
 
 
 
 
 
 
 
 
 
63
  """
64
  response = gemini_model.generate_content(prompt)
65
 
66
  try:
 
67
  cleaned_text = response.text.strip().replace("```json", "").replace("```", "")
68
  script_data = json.loads(cleaned_text)
69
  narration = script_data['narration_script']
 
76
  progress(0.3, desc="🎙️ Recording voiceover with ElevenLabs...")
77
  audio_path = f"audio_{job_id}.mp3"
78
  intermediate_files.append(audio_path)
79
+
80
+ # --- CHANGE 3: Use the client instance to generate audio ---
81
+ audio_bytes = elevenlabs_client.generate(
82
+ text=narration,
83
+ voice="Adam",
84
+ model="eleven_multilingual_v2"
85
+ )
86
  with open(audio_path, "wb") as f:
87
  f.write(audio_bytes)
88
  print(f"Audio file saved: {audio_path}")
89
 
90
+ # STEP 4: VISUALS (Runway)
91
  video_clip_paths = []
92
  for i, scene_prompt in enumerate(scene_prompts):
93
  progress(0.4 + (i * 0.12), desc=f"🎬 Generating video scene {i+1}/{len(scene_prompts)}...")
 
 
94
  runway_payload = {"text_prompt": scene_prompt}
95
  post_response = requests.post(RUNWAY_API_URL, headers=RUNWAY_HEADERS, json=runway_payload)
96
  if post_response.status_code != 200:
97
  raise gr.Error(f"Runway API Error (start job): {post_response.status_code} - {post_response.text}")
98
 
99
+ task_id = post_response.json().get("uuid")
 
100
  if not task_id:
101
+ raise gr.Error(f"Runway API did not return a task UUID. Response: {post_response.json()}")
102
 
 
103
  video_url = None
104
+ for _ in range(60):
105
  get_response = requests.get(f"{RUNWAY_API_URL}/{task_id}", headers=RUNWAY_HEADERS)
106
  status_details = get_response.json()
107
  status = status_details.get("status")
 
116
  time.sleep(10)
117
 
118
  if not video_url:
119
+ raise gr.Error(f"Runway job timed out for scene {i+1}.")
120
 
 
121
  clip_path = f"scene_{i+1}_{job_id}.mp4"
122
  intermediate_files.append(clip_path)
123
  video_clip_paths.append(clip_path)
 
130
 
131
  # STEP 5: STITCHING (FFmpeg)
132
  progress(0.9, desc="✂️ Assembling final video with FFmpeg...")
 
 
133
  file_list_path = f"file_list_{job_id}.txt"
134
  intermediate_files.append(file_list_path)
135
  with open(file_list_path, "w") as f:
136
  for clip in video_clip_paths:
137
  f.write(f"file '{clip}'\n")
138
 
 
139
  combined_video_path = f"combined_video_{job_id}.mp4"
140
  intermediate_files.append(combined_video_path)
141
  subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', file_list_path, '-c', 'copy', combined_video_path, '-y'], check=True)
142
 
 
143
  final_video_path = f"final_video_{job_id}.mp4"
 
144
  subprocess.run([
145
  'ffmpeg', '-i', combined_video_path, '-i', audio_path, '-c:v', 'copy',
146
  '-c:a', 'aac', '-shortest', final_video_path, '-y'
 
151
  return final_video_path
152
 
153
  except Exception as e:
 
154
  print(f"--- JOB {job_id} FAILED --- \nError: {e}")
155
  raise gr.Error(f"An error occurred: {e}")
156
 
157
  finally:
158
  # STEP 6: CLEANUP
 
159
  print("Cleaning up intermediate files...")
160
  for file_path in intermediate_files:
161
  if os.path.exists(file_path):
162
  os.remove(file_path)
163
  print(f"Removed: {file_path}")
164
 
 
165
  # --- 4. CREATE AND LAUNCH THE GRADIO INTERFACE ---
166
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
167
  gr.Markdown(