testdeep123 commited on
Commit
e827af3
·
verified ·
1 Parent(s): 15daf82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -59
app.py CHANGED
@@ -1,28 +1,35 @@
1
  # Import necessary libraries
2
- from kokoro import KPipeline
3
- import soundfile as sf
4
  import os
5
- from moviepy.editor import (
6
- VideoFileClip, concatenate_videoclips, AudioFileClip, ImageClip,
7
- CompositeVideoClip, TextClip
8
- )
9
- import moviepy.video.fx.all as vfx
10
- import moviepy.config as mpy_config
11
- from pydub import AudioSegment
12
- from PIL import Image
13
- import tempfile
14
  import random
15
- import cv2
16
  import math
 
 
17
  import requests
 
 
 
 
 
 
18
  from gtts import gTTS
19
  import gradio as gr
20
- import numpy as np
21
- import shutil
 
 
 
 
 
22
 
23
- # Initialize Kokoro TTS pipeline
24
- pipeline = KPipeline(lang_code='a')
25
- # Ensure ImageMagick binary is set (adjust path as needed)
 
 
 
 
 
26
  mpy_config.change_settings({"IMAGEMAGICK_BINARY": "/usr/bin/convert"})
27
 
28
  # Global Configuration
@@ -36,7 +43,9 @@ MAX_CLIPS = 10 # Maximum number of editable clips
36
  # Temporary folder setup
37
  TEMP_FOLDER = tempfile.mkdtemp()
38
 
39
- # Existing Helper Functions (unchanged)
 
 
40
  def generate_script(user_input):
41
  headers = {
42
  'Authorization': f'Bearer {OPENROUTER_API_KEY}',
@@ -95,10 +104,10 @@ Now here is the Topic/scrip: {user_input}
95
  return response.json()['choices'][0]['message']['content']
96
  else:
97
  print(f"API Error {response.status_code}: {response.text}")
98
- return None
99
  except Exception as e:
100
  print(f"Request failed: {str(e)}")
101
- return None
102
 
103
  def parse_script(script_text):
104
  sections = {}
@@ -110,7 +119,7 @@ def parse_script(script_text):
110
  bracket_start = line.find("[")
111
  bracket_end = line.find("]", bracket_start)
112
  if bracket_start != -1 and bracket_end != -1:
113
- if current_title is not None:
114
  sections[current_title] = current_text.strip()
115
  current_title = line[bracket_start+1:bracket_end]
116
  current_text = line[bracket_end+1:].strip()
@@ -121,6 +130,7 @@ def parse_script(script_text):
121
  clips = [{"title": title, "narration": narration} for title, narration in sections.items()]
122
  return clips
123
 
 
124
  def search_pexels_videos(query, pexels_api_key):
125
  headers = {'Authorization': pexels_api_key}
126
  url = "https://api.pexels.com/videos/search"
@@ -131,9 +141,10 @@ def search_pexels_videos(query, pexels_api_key):
131
  videos = response.json().get("videos", [])
132
  hd_videos = [v["video_files"][0]["link"] for v in videos if v["video_files"] and v["video_files"][0]["quality"] == "hd"]
133
  return random.choice(hd_videos) if hd_videos else None
 
134
  except Exception as e:
135
  print(f"Video search error: {e}")
136
- return None
137
 
138
  def search_pexels_images(query, pexels_api_key):
139
  headers = {'Authorization': pexels_api_key}
@@ -144,9 +155,10 @@ def search_pexels_images(query, pexels_api_key):
144
  if response.status_code == 200:
145
  photos = response.json().get("photos", [])
146
  return random.choice(photos)["src"]["original"] if photos else None
 
147
  except Exception as e:
148
  print(f"Image search error: {e}")
149
- return None
150
 
151
  def search_google_images(query):
152
  search_url = f"https://www.google.com/search?q={quote(query)}&tbm=isch"
@@ -188,9 +200,10 @@ def download_video(video_url, filename):
188
  print(f"Video download error: {e}")
189
  return None
190
 
 
191
  def generate_media(prompt, custom_media=None, video_prob=0.25):
192
- if custom_media:
193
- asset_type = "video" if custom_media.endswith(('.mp4', '.avi', '.mov')) else "image"
194
  return {"path": custom_media, "asset_type": asset_type}
195
  safe_prompt = re.sub(r'[^\w\s-]', '', prompt).strip().replace(' ', '_')
196
  if "news" in prompt.lower():
@@ -207,26 +220,33 @@ def generate_media(prompt, custom_media=None, video_prob=0.25):
207
  image_url = search_pexels_images(prompt, PEXELS_API_KEY)
208
  if image_url and download_image(image_url, image_file):
209
  return {"path": image_file, "asset_type": "image"}
 
210
  return None
211
 
212
- def generate_tts(text, voice):
 
 
213
  safe_text = re.sub(r'[^\w\s-]', '', text[:10]).strip().replace(' ', '_')
214
  file_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.wav")
215
  if os.path.exists(file_path):
216
  return file_path
217
  try:
218
- audio_segments = [audio for _, _, audio in pipeline(text, voice='af_heart', speed=0.9, split_pattern=r'\n+')]
219
- full_audio = np.concatenate(audio_segments) if len(audio_segments) > 1 else audio_segments[0]
220
- sf.write(file_path, full_audio, 24000)
221
- return file_path
222
- except Exception:
223
- tts = gTTS(text=text, lang='en')
224
- mp3_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.mp3")
225
- tts.save(mp3_path)
226
- AudioSegment.from_mp3(mp3_path).export(file_path, format="wav")
227
- os.remove(mp3_path)
228
  return file_path
 
 
 
229
 
 
230
  def apply_kenburns_effect(clip, target_resolution):
231
  target_w, target_h = target_resolution
232
  clip_aspect = clip.w / clip.h
@@ -266,8 +286,11 @@ def add_background_music(final_video, bgm_path=None, bgm_volume=0.15):
266
  return final_video
267
 
268
  def create_clip(media_path, asset_type, tts_path, narration_text, target_resolution, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect):
269
- audio_clip = AudioFileClip(tts_path).audio_fadeout(0.2)
270
- target_duration = audio_clip.duration + 0.2
 
 
 
271
  if asset_type == "video":
272
  clip = VideoFileClip(media_path)
273
  clip = resize_to_fill(clip, target_resolution)
@@ -277,10 +300,10 @@ def create_clip(media_path, asset_type, tts_path, narration_text, target_resolut
277
  if zoom_pan_effect:
278
  clip = apply_kenburns_effect(clip, target_resolution)
279
  clip = resize_to_fill(clip, target_resolution)
280
- if subtitles_enabled and narration_text:
281
  words = narration_text.split()
282
  chunks = [' '.join(words[i:i+5]) for i in range(0, len(words), 5)]
283
- chunk_duration = audio_clip.duration / len(chunks)
284
  subtitle_clips = []
285
  y_position = target_resolution[1] * (0.1 if position == "top" else 0.8 if position == "bottom" else 0.5)
286
  for i, chunk in enumerate(chunks):
@@ -297,9 +320,11 @@ def create_clip(media_path, asset_type, tts_path, narration_text, target_resolut
297
  ).set_start(i * chunk_duration).set_end((i + 1) * chunk_duration).set_position(('center', y_position))
298
  subtitle_clips.append(txt_clip)
299
  clip = CompositeVideoClip([clip] + subtitle_clips)
300
- return clip.set_audio(audio_clip)
 
 
301
 
302
- # Main Video Generation Function
303
  def generate_video(resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, *clip_data):
304
  target_resolution = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
305
  clips = []
@@ -309,21 +334,23 @@ def generate_video(resolution, render_speed, video_clip_percent, zoom_pan_effect
309
  media_asset = generate_media(prompt, custom_media, video_clip_percent / 100.0)
310
  if not media_asset:
311
  continue
312
- tts_path = generate_tts(narration, 'en')
313
  clip = create_clip(
314
  media_asset['path'], media_asset['asset_type'], tts_path, narration, target_resolution,
315
  subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect
316
  )
317
- clips.append(clip)
 
318
  if not clips:
 
319
  return None
320
  final_video = concatenate_videoclips(clips, method="compose")
321
  final_video = add_background_music(final_video, bgm_upload, bgm_volume)
322
- final_video.write_videofile(OUTPUT_VIDEO_FILENAME, codec='libx264', fps=24, preset=render_speed)
323
  shutil.rmtree(TEMP_FOLDER)
324
  return OUTPUT_VIDEO_FILENAME
325
 
326
- # Load Clips Function
327
  def load_clips(topic, script):
328
  raw_script = script.strip() if script.strip() else generate_script(topic)
329
  clips = parse_script(raw_script)[:MAX_CLIPS]
@@ -333,11 +360,14 @@ def load_clips(topic, script):
333
  updates.extend([
334
  gr.update(value=clips[i]["title"]),
335
  gr.update(value=clips[i]["narration"]),
336
- gr.update(value=None),
337
- gr.update(visible=True)
338
  ])
339
  else:
340
- updates.extend([gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(visible=False)])
 
 
 
 
341
  return updates
342
 
343
  # Gradio Interface
@@ -349,19 +379,18 @@ with gr.Blocks(title="🚀 Orbit Video Engine") as app:
349
  topic_input = gr.Textbox(label="Video Topic", placeholder="e.g., Funny Cat Facts")
350
  script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
351
  generate_script_btn = gr.Button("📝 Generate Script & Load Clips")
352
- generated_script_display = gr.Textbox(label="Generated Script", interactive=False)
353
 
354
  # Column 2: Clip Editor
355
  with gr.Column():
356
  gr.Markdown("### 2. Edit Clips")
357
  gr.Markdown("Modify prompts, narration, and upload custom media for each clip.")
358
- clip_rows, prompts, narrations, custom_medias = [], [], [], []
359
  for i in range(MAX_CLIPS):
360
- with gr.Row(visible=False) as row:
361
- prompt = gr.Textbox(label="Visual Prompt")
362
- narration = gr.Textbox(label="Narration", lines=3)
363
- custom_media = gr.File(label="Upload Custom Media (Image/Video)", file_types=["image", "video"])
364
- clip_rows.append(row)
365
  prompts.append(prompt)
366
  narrations.append(narration)
367
  custom_medias.append(custom_media)
@@ -390,14 +419,15 @@ with gr.Blocks(title="🚀 Orbit Video Engine") as app:
390
 
391
  # Event Handlers
392
  generate_script_btn.click(
393
- load_clips,
394
  inputs=[topic_input, script_input],
395
- outputs=[generated_script_display] + prompts + narrations + custom_medias + clip_rows
396
  )
397
  generate_video_btn.click(
398
- generate_video,
399
  inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position] + prompts + narrations + custom_medias,
400
  outputs=video_output
401
  )
402
 
 
403
  app.launch(share=True)
 
1
  # Import necessary libraries
 
 
2
  import os
3
+ import re
 
 
 
 
 
 
 
 
4
  import random
 
5
  import math
6
+ import shutil
7
+ import tempfile
8
  import requests
9
+ from urllib.parse import quote
10
+ from bs4 import BeautifulSoup
11
+ import numpy as np
12
+ from PIL import Image
13
+ import cv2
14
+ from pydub import AudioSegment
15
  from gtts import gTTS
16
  import gradio as gr
17
+ import soundfile as sf
18
+ from moviepy.editor import (
19
+ VideoFileClip, concatenate_videoclips, AudioFileClip, ImageClip,
20
+ CompositeVideoClip, TextClip, CompositeAudioClip
21
+ )
22
+ import moviepy.video.fx.all as vfx
23
+ import moviepy.config as mpy_config
24
 
25
+ # Initialize Kokoro TTS pipeline (assuming it's available; replace with dummy if not)
26
+ try:
27
+ from kokoro import KPipeline
28
+ pipeline = KPipeline(lang_code='a')
29
+ except ImportError:
30
+ pipeline = None # Fallback to gTTS if Kokoro is unavailable
31
+
32
+ # Ensure ImageMagick binary is set (adjust path as needed for your system)
33
  mpy_config.change_settings({"IMAGEMAGICK_BINARY": "/usr/bin/convert"})
34
 
35
  # Global Configuration
 
43
  # Temporary folder setup
44
  TEMP_FOLDER = tempfile.mkdtemp()
45
 
46
+ # Helper Functions
47
+
48
+ ## Script Generation
49
  def generate_script(user_input):
50
  headers = {
51
  'Authorization': f'Bearer {OPENROUTER_API_KEY}',
 
104
  return response.json()['choices'][0]['message']['content']
105
  else:
106
  print(f"API Error {response.status_code}: {response.text}")
107
+ return "Failed to generate script due to API error."
108
  except Exception as e:
109
  print(f"Request failed: {str(e)}")
110
+ return "Oops, script generation broke. Blame the internet!"
111
 
112
  def parse_script(script_text):
113
  sections = {}
 
119
  bracket_start = line.find("[")
120
  bracket_end = line.find("]", bracket_start)
121
  if bracket_start != -1 and bracket_end != -1:
122
+ if current_title:
123
  sections[current_title] = current_text.strip()
124
  current_title = line[bracket_start+1:bracket_end]
125
  current_text = line[bracket_end+1:].strip()
 
130
  clips = [{"title": title, "narration": narration} for title, narration in sections.items()]
131
  return clips
132
 
133
+ ## Media Fetching
134
  def search_pexels_videos(query, pexels_api_key):
135
  headers = {'Authorization': pexels_api_key}
136
  url = "https://api.pexels.com/videos/search"
 
141
  videos = response.json().get("videos", [])
142
  hd_videos = [v["video_files"][0]["link"] for v in videos if v["video_files"] and v["video_files"][0]["quality"] == "hd"]
143
  return random.choice(hd_videos) if hd_videos else None
144
+ return None
145
  except Exception as e:
146
  print(f"Video search error: {e}")
147
+ return None
148
 
149
  def search_pexels_images(query, pexels_api_key):
150
  headers = {'Authorization': pexels_api_key}
 
155
  if response.status_code == 200:
156
  photos = response.json().get("photos", [])
157
  return random.choice(photos)["src"]["original"] if photos else None
158
+ return None
159
  except Exception as e:
160
  print(f"Image search error: {e}")
161
+ return None
162
 
163
  def search_google_images(query):
164
  search_url = f"https://www.google.com/search?q={quote(query)}&tbm=isch"
 
200
  print(f"Video download error: {e}")
201
  return None
202
 
203
+ ## Media and TTS Generation
204
  def generate_media(prompt, custom_media=None, video_prob=0.25):
205
+ if isinstance(custom_media, str) and os.path.exists(custom_media):
206
+ asset_type = "video" if custom_media.lower().endswith(('.mp4', '.avi', '.mov')) else "image"
207
  return {"path": custom_media, "asset_type": asset_type}
208
  safe_prompt = re.sub(r'[^\w\s-]', '', prompt).strip().replace(' ', '_')
209
  if "news" in prompt.lower():
 
220
  image_url = search_pexels_images(prompt, PEXELS_API_KEY)
221
  if image_url and download_image(image_url, image_file):
222
  return {"path": image_file, "asset_type": "image"}
223
+ print(f"No media generated for prompt: {prompt}")
224
  return None
225
 
226
+ def generate_tts(text, voice='en'):
227
+ if not text.strip():
228
+ return None
229
  safe_text = re.sub(r'[^\w\s-]', '', text[:10]).strip().replace(' ', '_')
230
  file_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.wav")
231
  if os.path.exists(file_path):
232
  return file_path
233
  try:
234
+ if pipeline:
235
+ audio_segments = [audio for _, _, audio in pipeline(text, voice='af_heart', speed=0.9, split_pattern=r'\n+')]
236
+ full_audio = np.concatenate(audio_segments) if len(audio_segments) > 1 else audio_segments[0]
237
+ sf.write(file_path, full_audio, 24000)
238
+ else:
239
+ tts = gTTS(text=text, lang=voice)
240
+ mp3_path = os.path.join(TEMP_FOLDER, f"tts_{safe_text}.mp3")
241
+ tts.save(mp3_path)
242
+ AudioSegment.from_mp3(mp3_path).export(file_path, format="wav")
243
+ os.remove(mp3_path)
244
  return file_path
245
+ except Exception as e:
246
+ print(f"TTS generation failed: {e}")
247
+ return None
248
 
249
+ ## Video Processing
250
  def apply_kenburns_effect(clip, target_resolution):
251
  target_w, target_h = target_resolution
252
  clip_aspect = clip.w / clip.h
 
286
  return final_video
287
 
288
  def create_clip(media_path, asset_type, tts_path, narration_text, target_resolution, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect):
289
+ if not media_path or not os.path.exists(media_path) or (tts_path and not os.path.exists(tts_path)):
290
+ print("Missing media or TTS file")
291
+ return None
292
+ audio_clip = AudioFileClip(tts_path).audio_fadeout(0.2) if tts_path else None
293
+ target_duration = audio_clip.duration + 0.2 if audio_clip else 5.0
294
  if asset_type == "video":
295
  clip = VideoFileClip(media_path)
296
  clip = resize_to_fill(clip, target_resolution)
 
300
  if zoom_pan_effect:
301
  clip = apply_kenburns_effect(clip, target_resolution)
302
  clip = resize_to_fill(clip, target_resolution)
303
+ if subtitles_enabled and narration_text and audio_clip:
304
  words = narration_text.split()
305
  chunks = [' '.join(words[i:i+5]) for i in range(0, len(words), 5)]
306
+ chunk_duration = audio_clip.duration / max(len(chunks), 1)
307
  subtitle_clips = []
308
  y_position = target_resolution[1] * (0.1 if position == "top" else 0.8 if position == "bottom" else 0.5)
309
  for i, chunk in enumerate(chunks):
 
320
  ).set_start(i * chunk_duration).set_end((i + 1) * chunk_duration).set_position(('center', y_position))
321
  subtitle_clips.append(txt_clip)
322
  clip = CompositeVideoClip([clip] + subtitle_clips)
323
+ if audio_clip:
324
+ clip = clip.set_audio(audio_clip)
325
+ return clip
326
 
327
+ ## Main Video Generation Function
328
  def generate_video(resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, *clip_data):
329
  target_resolution = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
330
  clips = []
 
334
  media_asset = generate_media(prompt, custom_media, video_clip_percent / 100.0)
335
  if not media_asset:
336
  continue
337
+ tts_path = generate_tts(narration) if narration.strip() else None
338
  clip = create_clip(
339
  media_asset['path'], media_asset['asset_type'], tts_path, narration, target_resolution,
340
  subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position, zoom_pan_effect
341
  )
342
+ if clip:
343
+ clips.append(clip)
344
  if not clips:
345
+ print("No clips generated.")
346
  return None
347
  final_video = concatenate_videoclips(clips, method="compose")
348
  final_video = add_background_music(final_video, bgm_upload, bgm_volume)
349
+ final_video.write_videofile(OUTPUT_VIDEO_FILENAME, codec='libx264', fps=24, preset=render_speed, logger=None)
350
  shutil.rmtree(TEMP_FOLDER)
351
  return OUTPUT_VIDEO_FILENAME
352
 
353
+ ## Load Clips Function
354
  def load_clips(topic, script):
355
  raw_script = script.strip() if script.strip() else generate_script(topic)
356
  clips = parse_script(raw_script)[:MAX_CLIPS]
 
360
  updates.extend([
361
  gr.update(value=clips[i]["title"]),
362
  gr.update(value=clips[i]["narration"]),
363
+ gr.update(value=None) # Clear custom media
 
364
  ])
365
  else:
366
+ updates.extend([
367
+ gr.update(value=""),
368
+ gr.update(value=""),
369
+ gr.update(value=None)
370
+ ])
371
  return updates
372
 
373
  # Gradio Interface
 
379
  topic_input = gr.Textbox(label="Video Topic", placeholder="e.g., Funny Cat Facts")
380
  script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
381
  generate_script_btn = gr.Button("📝 Generate Script & Load Clips")
382
+ generated_script_display = gr.Textbox(label="Generated Script", interactive=False, lines=10)
383
 
384
  # Column 2: Clip Editor
385
  with gr.Column():
386
  gr.Markdown("### 2. Edit Clips")
387
  gr.Markdown("Modify prompts, narration, and upload custom media for each clip.")
388
+ prompts, narrations, custom_medias = [], [], []
389
  for i in range(MAX_CLIPS):
390
+ with gr.Row(): # Always visible
391
+ prompt = gr.Textbox(label=f"Clip {i+1} Visual Prompt")
392
+ narration = gr.Textbox(label=f"Clip {i+1} Narration", lines=3)
393
+ custom_media = gr.File(label=f"Clip {i+1} Upload Custom Media (Image/Video)", file_types=["image", "video"], value=None)
 
394
  prompts.append(prompt)
395
  narrations.append(narration)
396
  custom_medias.append(custom_media)
 
419
 
420
  # Event Handlers
421
  generate_script_btn.click(
422
+ fn=load_clips,
423
  inputs=[topic_input, script_input],
424
+ outputs=[generated_script_display] + prompts + narrations + custom_medias
425
  )
426
  generate_video_btn.click(
427
+ fn=generate_video,
428
  inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect, bgm_upload, bgm_volume, subtitles_enabled, font, font_size, outline_width, font_color, outline_color, position] + prompts + narrations + custom_medias,
429
  outputs=video_output
430
  )
431
 
432
+ # Launch the app
433
  app.launch(share=True)