shaheerawan3 commited on
Commit
c907607
·
verified ·
1 Parent(s): 454b9fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +568 -200
app.py CHANGED
@@ -1,246 +1,614 @@
1
  import streamlit as st
 
2
  import torch
3
  from transformers import pipeline
4
  from PIL import Image, ImageDraw, ImageFont
5
- from pathlib import Path
6
  import tempfile
7
- import textwrap
 
8
  import numpy as np
9
- from moviepy.editor import VideoFileClip, AudioFileClip, concatenate_videoclips, VideoClip, ColorClip, CompositeVideoClip, TextClip
10
  from gtts import gTTS
11
- import os
12
  from concurrent.futures import ThreadPoolExecutor
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- class VideoGenerator:
15
  def __init__(self):
16
- # Initialize device
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
18
-
19
- # Create temp directory for files
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  self.temp_dir = Path(tempfile.mkdtemp())
21
-
22
- # Initialize text generator
23
- self.text_generator = pipeline(
24
- 'text-generation',
25
- model='gpt2', # Using GPT2 for better text generation
26
- device=0 if self.device == "cuda" else -1
27
- )
28
-
29
- # Try to load a system font, fallback to default if none available
30
- self.font_size = 40
31
  try:
32
- font_paths = [
33
- "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", # Linux
34
- "/Library/Fonts/Arial.ttf", # MacOS
35
- "C:\\Windows\\Fonts\\arial.ttf" # Windows
 
36
  ]
37
- for font_path in font_paths:
38
- if os.path.exists(font_path):
39
- self.font = ImageFont.truetype(font_path, self.font_size)
 
40
  break
 
 
41
  else:
42
  self.font = ImageFont.load_default()
43
- except:
44
- self.font = ImageFont.load_default()
45
-
46
- # Define color schemes
47
- self.color_schemes = {
48
- 'professional': {
49
- 'bg': (245, 245, 245),
50
- 'text': (33, 33, 33),
51
- 'accent': (0, 120, 212)
52
- },
53
- 'creative': {
54
- 'bg': (255, 253, 244),
55
- 'text': (41, 41, 41),
56
- 'accent': (255, 126, 103)
57
  }
58
- }
59
 
60
- def generate_script(self, topic, duration):
61
- """Generate video script from topic"""
 
 
 
 
62
  try:
63
- prompt = f"Create a clear and engaging video script about {topic}. Make it informative and concise."
64
-
65
- output = self.text_generator(
66
- prompt,
67
- max_length=min(duration * 5, 1000), # Adjust length based on duration
68
- num_return_sequences=1,
69
- temperature=0.7,
70
- pad_token_id=50256
71
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- script = output[0]['generated_text'].replace(prompt, '').strip()
74
- return script
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  except Exception as e:
77
- st.error(f"Script generation failed: {str(e)}")
78
- return None
 
79
 
80
- def create_text_frame(self, text, frame_size=(1280, 720), color_scheme='professional'):
81
- """Create a single frame with text overlay"""
82
- # Create base frame
83
- frame = Image.new('RGB', frame_size, self.color_schemes[color_scheme]['bg'])
84
- draw = ImageDraw.Draw(frame)
85
-
86
- # Wrap text to fit screen
87
- margin = 100
88
- wrap_width = int((frame_size[0] - 2 * margin) / (self.font_size * 0.6))
89
- wrapped_text = textwrap.fill(text, width=wrap_width)
90
-
91
- # Get text size and calculate position
92
- bbox = draw.textbbox((0, 0), wrapped_text, font=self.font)
93
- text_width = bbox[2] - bbox[0]
94
- text_height = bbox[3] - bbox[1]
95
-
96
- x = (frame_size[0] - text_width) // 2
97
- y = (frame_size[1] - text_height) // 2
98
-
99
- # Draw text with a slight shadow for better visibility
100
- shadow_offset = 2
101
- draw.text((x+shadow_offset, y+shadow_offset), wrapped_text,
102
- fill=(200, 200, 200), font=self.font)
103
- draw.text((x, y), wrapped_text,
104
- fill=self.color_schemes[color_scheme]['text'], font=self.font)
105
-
106
- return np.array(frame)
107
-
108
- def create_subtitle_clip(self, text, duration, video_size=(1280, 720)):
109
- """Create a subtitle clip"""
110
- return TextClip(
111
- text,
112
- font='Arial',
113
- fontsize=30,
114
- color='white',
115
- bg_color='black',
116
- size=(video_size[0], 50),
117
- method='caption'
118
- ).set_duration(duration).set_position(('center', 'bottom'))
119
-
120
- def create_video(self, script, duration):
121
- """Create full video with visuals and audio"""
122
  try:
123
- # Split script into sentences
124
- sentences = [s.strip() for s in script.split('.') if s.strip()]
125
- sentence_duration = duration / len(sentences)
126
-
127
- # Create video clips
128
- video_size = (1280, 720)
129
- fps = 24
130
-
131
- def make_frame_for_sentence(sentence):
132
- """Create video clip for a single sentence"""
133
- # Create base frame
134
- frame = self.create_text_frame(sentence, video_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- # Create video clip
137
- clip = VideoClip(lambda t: frame, duration=sentence_duration)
 
 
138
 
139
- # Add subtitle
140
- subtitle = self.create_subtitle_clip(sentence, sentence_duration, video_size)
141
- return CompositeVideoClip([clip, subtitle])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # Generate all clips in parallel
144
- with ThreadPoolExecutor() as executor:
145
- video_clips = list(executor.map(make_frame_for_sentence, sentences))
146
 
147
- # Concatenate all clips
148
- final_video = concatenate_videoclips(video_clips)
 
 
149
 
150
- # Generate audio narration
151
- audio_path = self.temp_dir / "narration.mp3"
152
- tts = gTTS(text=script, lang='en', slow=False)
153
- tts.save(str(audio_path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- # Add audio to video
156
- audio_clip = AudioFileClip(str(audio_path))
157
- final_video = final_video.set_audio(audio_clip)
158
 
 
 
 
 
 
 
 
 
159
  # Write final video
160
- output_path = self.temp_dir / "output.mp4"
161
- final_video.write_videofile(
162
- str(output_path),
163
  fps=fps,
164
  codec='libx264',
165
- audio_codec='aac'
 
 
166
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- # Cleanup
169
- audio_clip.close()
170
- final_video.close()
 
 
171
 
172
- return output_path
 
173
 
174
  except Exception as e:
175
- st.error(f"Video creation failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  return None
177
 
178
- def main():
179
- st.set_page_config(page_title="Video Generator", layout="wide")
180
-
181
- st.title("📽️ AI Video Generator")
182
- st.write("Generate professional videos with narration and subtitles!")
183
-
184
- # Initialize video generator
185
- if 'generator' not in st.session_state:
186
- st.session_state.generator = VideoGenerator()
187
-
188
- # Input section
189
- col1, col2 = st.columns([3, 1])
190
-
191
- with col1:
192
- topic = st.text_area("Enter your video topic", height=100)
193
-
194
- with col2:
195
- duration = st.slider("Duration (seconds)", 30, 180, 60)
196
- style = st.selectbox("Style", ['professional', 'creative'])
197
-
198
- # Generate button
199
- if st.button("🎬 Generate Video"):
200
- if topic:
201
- try:
202
- with st.spinner("Generating your video..."):
203
- # Progress bar
204
- progress = st.progress(0)
205
-
206
- # Generate script
207
- script = st.session_state.generator.generate_script(topic, duration)
208
- if not script:
209
- st.error("Failed to generate script. Please try again.")
210
- return
211
- progress.progress(33)
212
-
213
- # Create video
214
- video_path = st.session_state.generator.create_video(script, duration)
215
- if not video_path:
216
- st.error("Failed to create video. Please try again.")
217
- return
218
- progress.progress(100)
219
-
220
- # Display results
221
- st.success("Video generated successfully!")
222
-
223
- # Show video and script
224
- col1, col2 = st.columns(2)
225
- with col1:
226
- st.video(str(video_path))
227
- with col2:
228
- st.subheader("Generated Script")
229
- st.write(script)
230
-
231
- # Download button
232
- with open(str(video_path), 'rb') as f:
233
- st.download_button(
234
- "⬇️ Download Video",
235
- f,
236
- file_name="generated_video.mp4",
237
- mime="video/mp4"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  )
239
 
240
- except Exception as e:
241
- st.error(f"An error occurred: {str(e)}")
242
- else:
243
- st.warning("Please enter a topic first!")
 
 
 
 
 
 
 
 
 
 
244
 
245
  if __name__ == "__main__":
246
- main()
 
1
  import streamlit as st
2
+ from pathlib import Path
3
  import torch
4
  from transformers import pipeline
5
  from PIL import Image, ImageDraw, ImageFont
 
6
  import tempfile
7
+ import os
8
+ from moviepy.editor import *
9
  import numpy as np
 
10
  from gtts import gTTS
11
+ import textwrap
12
  from concurrent.futures import ThreadPoolExecutor
13
+ import io
14
+ import unicodedata
15
+ import re
16
+ import requests
17
+ from stability_sdk import client
18
+ import stability_sdk.interfaces.gooseai.generation.generation_pb2 as generation
19
+ import random
20
+ from pexels_api import API
21
+ from moviepy.video.fx.all import crop, resize
22
+ import logging
23
+ from typing import Optional, List, Dict, Tuple
24
+ import json
25
 
26
+ class EnhancedVideoGenerator:
27
  def __init__(self):
28
+ """Initialize the video generator with all required components"""
29
+ try:
30
+ self.setup_logging()
31
+ self.setup_device()
32
+ self.initialize_models()
33
+ self.setup_workspace()
34
+ self.load_assets()
35
+ self.setup_api_clients()
36
+ except Exception as e:
37
+ logging.error(f"Initialization failed: {str(e)}")
38
+ raise RuntimeError("Failed to initialize video generator")
39
+
40
+ def setup_logging(self):
41
+ """Configure logging for the application"""
42
+ logging.basicConfig(
43
+ level=logging.INFO,
44
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
45
+ handlers=[
46
+ logging.FileHandler('video_generator.log'),
47
+ logging.StreamHandler()
48
+ ]
49
+ )
50
+ self.logger = logging.getLogger(__name__)
51
+
52
+ def setup_device(self):
53
+ """Set up computing device (CPU/GPU)"""
54
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
55
+ self.logger.info(f"Using device: {self.device}")
56
+
57
+ def initialize_models(self):
58
+ """Initialize all AI models"""
59
+ try:
60
+ # Text generation model
61
+ self.text_generator = pipeline(
62
+ 'text-generation',
63
+ model='gpt2-medium', # Upgraded from distilgpt2
64
+ device=0 if self.device == "cuda" else -1
65
+ )
66
+
67
+ # Voice generation model (optional)
68
+ if os.getenv("ELEVENLABS_API_KEY"):
69
+ from elevenlabs import set_api_key
70
+ set_api_key(os.getenv("ELEVENLABS_API_KEY"))
71
+ self.use_premium_voice = True
72
+ else:
73
+ self.use_premium_voice = False
74
+
75
+ except Exception as e:
76
+ self.logger.error(f"Model initialization failed: {str(e)}")
77
+ raise
78
+
79
+ def setup_workspace(self):
80
+ """Set up working directory and resources"""
81
  self.temp_dir = Path(tempfile.mkdtemp())
82
+ self.asset_dir = self.temp_dir / "assets"
83
+ self.asset_dir.mkdir(exist_ok=True)
84
+
85
+ def load_assets(self):
86
+ """Load visual assets and fonts"""
 
 
 
 
 
87
  try:
88
+ # Try multiple font options
89
+ font_options = [
90
+ "arial.ttf",
91
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
92
+ "/System/Library/Fonts/Helvetica.ttc"
93
  ]
94
+
95
+ for font_path in font_options:
96
+ try:
97
+ self.font = ImageFont.truetype(font_path, 40)
98
  break
99
+ except OSError:
100
+ continue
101
  else:
102
  self.font = ImageFont.load_default()
103
+ self.logger.warning("Using default font - custom font loading failed")
104
+
105
+ # Load transition effects
106
+ self.transitions = {
107
+ 'fade': VideoFileClip("assets/transitions/fade.mp4"),
108
+ 'slide': VideoFileClip("assets/transitions/slide.mp4"),
109
+ 'dissolve': VideoFileClip("assets/transitions/dissolve.mp4")
 
 
 
 
 
 
 
110
  }
 
111
 
112
+ except Exception as e:
113
+ self.logger.error(f"Asset loading failed: {str(e)}")
114
+ self.transitions = {} # Fallback to no transitions
115
+
116
+ def setup_api_clients(self):
117
+ """Initialize API clients for external services"""
118
  try:
119
+ # Pexels API for stock footage
120
+ pexels_api_key = os.getenv("PEXELS_API_KEY")
121
+ if pexels_api_key:
122
+ self.pexels = API(pexels_api_key)
123
+ else:
124
+ self.logger.warning("No Pexels API key found - stock footage disabled")
125
+ self.pexels = None
126
+
127
+ # Stability AI for image generation
128
+ stability_api_key = os.getenv("STABILITY_API_KEY")
129
+ if stability_api_key:
130
+ self.stability_api = client.StabilityInference(
131
+ key=stability_api_key,
132
+ engine="stable-diffusion-xl-1024-v1-0"
133
+ )
134
+ else:
135
+ self.logger.warning("No Stability AI key found - AI image generation disabled")
136
+ self.stability_api = None
137
+
138
+ except Exception as e:
139
+ self.logger.error(f"API client setup failed: {str(e)}")
140
+
141
+ def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
142
+ """Generate or fetch relevant visual assets based on script content"""
143
+ try:
144
+ # Extract key topics from script
145
+ topics = self.extract_key_topics(script)
146
 
147
+ assets = []
148
+ for topic in topics:
149
+ # Try to get stock footage first
150
+ if self.pexels:
151
+ video = self.pexels.search_videos(
152
+ topic,
153
+ orientation='landscape',
154
+ size='large',
155
+ per_page=1
156
+ )
157
+ if video and video.videos:
158
+ assets.append({
159
+ 'type': 'video',
160
+ 'url': video.videos[0].url,
161
+ 'topic': topic
162
+ })
163
+ continue
164
+
165
+ # Fallback to AI-generated image
166
+ if self.stability_api:
167
+ image = self.generate_ai_image(topic, style)
168
+ if image:
169
+ assets.append({
170
+ 'type': 'image',
171
+ 'data': image,
172
+ 'topic': topic
173
+ })
174
+
175
+ return assets
176
+
177
+ except Exception as e:
178
+ self.logger.error(f"Visual asset generation failed: {str(e)}")
179
+ return []
180
+
181
+ def create_enhanced_frame(
182
+ self,
183
+ text: str,
184
+ theme: dict,
185
+ frame_number: int,
186
+ total_frames: int,
187
+ background_image: Optional[Image.Image] = None,
188
+ size: Tuple[int, int] = (1920, 1080) # Upgraded to 1080p
189
+ ) -> np.ndarray:
190
+ """Create a visually enhanced frame with background, text, and effects"""
191
+ try:
192
+ # Create base frame
193
+ if background_image:
194
+ # Resize and crop background to fit
195
+ bg = background_image.resize(size, Image.LANCZOS)
196
+ frame = np.array(bg)
197
+ else:
198
+ frame = np.full((size[1], size[0], 3), theme['bg'], dtype=np.uint8)
199
+
200
+ # Convert to PIL Image for drawing
201
+ img = Image.fromarray(frame)
202
+ draw = ImageDraw.Draw(img, 'RGBA')
203
+
204
+ # Add subtle gradient overlay
205
+ overlay = Image.new('RGBA', size, (0, 0, 0, 0))
206
+ overlay_draw = ImageDraw.Draw(overlay)
207
+ overlay_draw.rectangle(
208
+ [0, 0, size[0], size[1]],
209
+ fill=(255, 255, 255, 100) # Semi-transparent white
210
+ )
211
+ img = Image.alpha_composite(img.convert('RGBA'), overlay)
212
+
213
+ # Add text with improved styling
214
+ text = self.clean_text(text)
215
+ wrapped_text = textwrap.fill(text, width=50)
216
 
217
+ # Calculate text position
218
+ text_bbox = draw.textbbox((0, 0), wrapped_text, font=self.font)
219
+ text_width = text_bbox[2] - text_bbox[0]
220
+ text_height = text_bbox[3] - text_bbox[1]
221
+ text_x = (size[0] - text_width) // 2
222
+ text_y = size[1] - text_height - 100 # Position at bottom
223
+
224
+ # Draw text background
225
+ padding = 20
226
+ draw.rectangle(
227
+ [
228
+ text_x - padding,
229
+ text_y - padding,
230
+ text_x + text_width + padding,
231
+ text_y + text_height + padding
232
+ ],
233
+ fill=(0, 0, 0, 160) # Semi-transparent black
234
+ )
235
+
236
+ # Draw text
237
+ draw.text(
238
+ (text_x, text_y),
239
+ wrapped_text,
240
+ fill=(255, 255, 255, 255),
241
+ font=self.font
242
+ )
243
+
244
+ # Add progress bar with animation
245
+ self.draw_animated_progress_bar(
246
+ draw,
247
+ frame_number,
248
+ total_frames,
249
+ size,
250
+ theme
251
+ )
252
+
253
+ return np.array(img)
254
+
255
  except Exception as e:
256
+ self.logger.error(f"Frame creation failed: {str(e)}")
257
+ # Return fallback frame
258
+ return np.full((size[1], size[0], 3), theme['bg'], dtype=np.uint8)
259
 
260
+ def draw_animated_progress_bar(
261
+ self,
262
+ draw: ImageDraw.Draw,
263
+ frame_number: int,
264
+ total_frames: int,
265
+ size: Tuple[int, int],
266
+ theme: dict
267
+ ):
268
+ """Draw an animated progress bar with effects"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  try:
270
+ progress = frame_number / total_frames
271
+ bar_width = int(size[0] * 0.8) # 80% of screen width
272
+ bar_height = 6
273
+ x_offset = (size[0] - bar_width) // 2
274
+ y_position = size[1] - 40
275
+
276
+ # Draw background bar
277
+ draw.rectangle(
278
+ [x_offset, y_position, x_offset + bar_width, y_position + bar_height],
279
+ fill=(200, 200, 200, 160)
280
+ )
281
+
282
+ # Draw progress with gradient effect
283
+ progress_width = int(bar_width * progress)
284
+ for x in range(progress_width):
285
+ alpha = int(255 * (x / bar_width)) # Gradient effect
286
+ draw.line(
287
+ [x_offset + x, y_position, x_offset + x, y_position + bar_height],
288
+ fill=(theme['accent'][0], theme['accent'][1], theme['accent'][2], alpha)
289
+ )
290
+
291
+ # Add animated highlight
292
+ highlight_pos = x_offset + progress_width
293
+ if highlight_pos < x_offset + bar_width:
294
+ draw.rectangle(
295
+ [highlight_pos-2, y_position-1, highlight_pos+2, y_position + bar_height+1],
296
+ fill=(255, 255, 255, 200)
297
+ )
298
+
299
+ except Exception as e:
300
+ self.logger.error(f"Progress bar drawing failed: {str(e)}")
301
+
302
+ def generate_voice_over(self, script: str) -> AudioFileClip:
303
+ """Generate voice-over audio with enhanced voice options"""
304
+ try:
305
+ if self.use_premium_voice:
306
+ # Use ElevenLabs for premium voice
307
+ from elevenlabs import generate
308
+ audio = generate(
309
+ text=script,
310
+ voice="Josh", # Professional male voice
311
+ model="eleven_monolingual_v1"
312
+ )
313
 
314
+ # Save to temp file
315
+ audio_path = self.temp_dir / "voice.mp3"
316
+ with open(audio_path, 'wb') as f:
317
+ f.write(audio)
318
 
319
+ else:
320
+ # Fallback to gTTS with improved settings
321
+ audio_path = self.temp_dir / "voice.mp3"
322
+ tts = gTTS(
323
+ text=script,
324
+ lang='en',
325
+ slow=False,
326
+ tld='com' # Use US English voice
327
+ )
328
+ tts.save(str(audio_path))
329
+
330
+ return AudioFileClip(str(audio_path))
331
+
332
+ except Exception as e:
333
+ self.logger.error(f"Voice-over generation failed: {str(e)}")
334
+ # Return silent audio as fallback
335
+ return AudioFileClip(duration=len(script.split()) * 0.3)
336
+
337
+ def create_video(
338
+ self,
339
+ script: str,
340
+ style: str,
341
+ duration: int,
342
+ output_path: str
343
+ ) -> str:
344
+ """Create full video with all enhanced features"""
345
+ try:
346
+ # Generate visual assets
347
+ assets = self.generate_visual_assets(script, style)
348
 
349
+ # Generate voice-over
350
+ audio = self.generate_voice_over(script)
 
351
 
352
+ # Create frames with visual assets
353
+ frames = []
354
+ fps = 30
355
+ total_frames = int(duration * fps)
356
 
357
+ with ThreadPoolExecutor() as executor:
358
+ frame_futures = []
359
+
360
+ for i in range(total_frames):
361
+ # Calculate current text segment
362
+ progress = i / total_frames
363
+ text_index = int(progress * len(script.split()))
364
+ current_text = " ".join(script.split()[:text_index + 1])
365
+
366
+ # Get appropriate background
367
+ asset_index = int(progress * len(assets))
368
+ current_asset = assets[asset_index] if assets else None
369
+
370
+ # Submit frame creation to thread pool
371
+ future = executor.submit(
372
+ self.create_enhanced_frame,
373
+ current_text,
374
+ self.themes[style],
375
+ i,
376
+ total_frames,
377
+ current_asset['data'] if current_asset and current_asset['type'] == 'image' else None
378
+ )
379
+ frame_futures.append(future)
380
+
381
+ # Collect frames
382
+ frames = [future.result() for future in frame_futures]
383
+
384
+ # Create video clip
385
+ video = ImageSequenceClip(frames, fps=fps)
386
 
387
+ # Add voice-over
388
+ video = video.set_audio(audio)
 
389
 
390
+ # Add background music (if available)
391
+ try:
392
+ music = AudioFileClip("assets/music/background.mp3")
393
+ music = music.volumex(0.1).loop(duration=video.duration)
394
+ video = video.set_audio(CompositeAudioClip([video.audio, music]))
395
+ except Exception as e:
396
+ self.logger.warning(f"Background music addition failed: {str(e)}")
397
+
398
  # Write final video
399
+ video.write_videofile(
400
+ output_path,
 
401
  fps=fps,
402
  codec='libx264',
403
+ audio_codec='aac',
404
+ threads=4,
405
+ preset='medium'
406
  )
407
+
408
+ return output_path
409
+
410
+ except Exception as e:
411
+ self.logger.error(f"Video creation failed: {str(e)}")
412
+ raise
413
+
414
+ @staticmethod
415
+ def clean_text(text: str) -> str:
416
+ """Clean and normalize text for display"""
417
+ if not isinstance(text, str):
418
+ text = str(text)
419
+
420
+ # Normalize unicode characters
421
+ text = unicodedata.normalize('NFKD', text)
422
+
423
+ # Remove non-ASCII characters
424
+ text = text.encode('ascii', 'ignore').decode('ascii')
425
+
426
+ # Replace problematic characters
427
+ replacements = {
428
+ '–': '-', # en dash
429
+ '—': '-', # em dash
430
+ '"': '"', # smart quotes
431
+ '"': '"', # smart quotes
432
+ ''': "'", # smart apostrophe
433
+ ''': "'", # smart apostrophe
434
+ '…': '...', # ellipsis
435
+ }
436
+ for old, new in replacements.items():
437
+ text = text.replace(old, new)
438
+
439
+ # Remove any remaining non-standard characters
440
+ text = re.sub(r'[^\x00-\x7F]+', '', text)
441
+
442
+ return text.strip()
443
+
444
+ def extract_key_topics(self, script: str) -> List[str]:
445
+ """Extract main topics from the script for visual asset generation"""
446
+ try:
447
+ # Simple keyword extraction based on noun phrases
448
+ # In a production environment, you might want to use a proper NLP library
449
+ sentences = script.split('.')
450
+ topics = []
451
 
452
+ for sentence in sentences:
453
+ words = sentence.strip().split()
454
+ if len(words) >= 2:
455
+ # Extract potential noun phrases (pairs of words)
456
+ topics.append(' '.join(words[:2]))
457
 
458
+ # Remove duplicates and limit to top 5 topics
459
+ return list(dict.fromkeys(topics))[:5]
460
 
461
  except Exception as e:
462
+ self.logger.error(f"Topic extraction failed: {str(e)}")
463
+ return ["default topic"]
464
+
465
+ def generate_ai_image(self, prompt: str, style: str) -> Optional[Image.Image]:
466
+ """Generate an AI image using Stability AI"""
467
+ try:
468
+ if not self.stability_api:
469
+ return None
470
+
471
+ # Enhance prompt based on style
472
+ style_prompts = {
473
+ 'Professional': "professional, corporate, clean, modern",
474
+ 'Creative': "artistic, vibrant, innovative, dynamic",
475
+ 'Educational': "clear, informative, academic, detailed"
476
+ }
477
+
478
+ enhanced_prompt = f"{prompt}, {style_prompts.get(style, '')}, high quality, 4k"
479
+
480
+ # Generate image
481
+ response = self.stability_api.generate(
482
+ prompt=enhanced_prompt,
483
+ samples=1,
484
+ width=1920,
485
+ height=1080
486
+ )
487
+
488
+ if response and len(response) > 0:
489
+ image_data = response[0].image
490
+ return Image.open(io.BytesIO(image_data))
491
+
492
  return None
493
 
494
+ except Exception as e:
495
+ self.logger.error(f"AI image generation failed: {str(e)}")
496
+ return None
497
+
498
+ def cleanup(self):
499
+ """Clean up temporary files and resources"""
500
+ try:
501
+ for file in self.temp_dir.glob('*'):
502
+ try:
503
+ if file.is_file():
504
+ file.unlink()
505
+ elif file.is_dir():
506
+ import shutil
507
+ shutil.rmtree(file)
508
+ except Exception as e:
509
+ self.logger.warning(f"Failed to delete {file}: {str(e)}")
510
+
511
+ self.temp_dir.rmdir()
512
+
513
+ except Exception as e:
514
+ self.logger.error(f"Cleanup failed: {str(e)}")
515
+
516
+ def __enter__(self):
517
+ return self
518
+
519
+ def __exit__(self, exc_type, exc_val, exc_tb):
520
+ self.cleanup()
521
+
522
+ # Streamlit UI Class
523
+ class VideoGeneratorUI:
524
+ def __init__(self):
525
+ self.generator = EnhancedVideoGenerator()
526
+ self.setup_ui()
527
+
528
+ def setup_ui(self):
529
+ st.title("Enhanced Video Generator")
530
+ st.write("Create professional videos with AI-generated content")
531
+
532
+ with st.form("video_generator_form"):
533
+ # Input fields
534
+ prompt = st.text_area(
535
+ "Enter your video topic/prompt",
536
+ height=100,
537
+ help="Describe what you want your video to be about"
538
+ )
539
+
540
+ col1, col2 = st.columns(2)
541
+ with col1:
542
+ style = st.selectbox(
543
+ "Choose style",
544
+ options=list(self.generator.themes.keys())
545
+ )
546
+
547
+ with col2:
548
+ duration = st.slider(
549
+ "Video duration (seconds)",
550
+ min_value=10,
551
+ max_value=300,
552
+ value=60,
553
+ step=10
554
+ )
555
+
556
+ advanced_options = st.expander("Advanced Options")
557
+ with advanced_options:
558
+ use_premium_voice = st.checkbox(
559
+ "Use premium voice-over",
560
+ value=False,
561
+ help="Requires ElevenLabs API key"
562
+ )
563
+
564
+ include_music = st.checkbox(
565
+ "Include background music",
566
+ value=True
567
+ )
568
+
569
+ fps = st.slider(
570
+ "Frames per second",
571
+ min_value=24,
572
+ max_value=60,
573
+ value=30
574
+ )
575
+
576
+ submit_button = st.form_submit_button("Generate Video")
577
+
578
+ if submit_button:
579
+ if not prompt:
580
+ st.error("Please enter a prompt for your video.")
581
+ return
582
+
583
+ try:
584
+ with st.spinner("Generating your video..."):
585
+ output_path = f"generated_video_{int(time.time())}.mp4"
586
+
587
+ # Update generator settings based on advanced options
588
+ self.generator.use_premium_voice = use_premium_voice
589
+
590
+ # Generate video
591
+ video_path = self.generator.create_video(
592
+ prompt,
593
+ style,
594
+ duration,
595
+ output_path
596
  )
597
 
598
+ # Show success message and download button
599
+ st.success("Video generated successfully!")
600
+
601
+ with open(video_path, 'rb') as f:
602
+ st.download_button(
603
+ label="Download Video",
604
+ data=f.read(),
605
+ file_name=output_path,
606
+ mime="video/mp4"
607
+ )
608
+
609
+ except Exception as e:
610
+ st.error(f"Failed to generate video: {str(e)}")
611
+ st.error("Please try again with different settings or contact support.")
612
 
613
  if __name__ == "__main__":
614
+ ui = VideoGeneratorUI()