shaheerawan3 commited on
Commit
4c33be9
·
verified ·
1 Parent(s): 49e3402

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -501
app.py CHANGED
@@ -1,225 +1,94 @@
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
@@ -230,385 +99,176 @@ class EnhancedVideoGenerator:
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()
 
1
  import streamlit as st
2
+ import numpy as np
 
 
3
  from PIL import Image, ImageDraw, ImageFont
4
  import tempfile
5
  import os
6
+ from pathlib import Path
 
 
7
  import textwrap
8
+ from gtts import gTTS
9
+ from moviepy.editor import VideoFileClip, ImageSequenceClip, AudioFileClip, CompositeVideoClip
 
 
 
 
 
 
 
 
10
  import logging
11
+ from typing import Optional, Tuple
12
+ import io
13
 
14
+ class SimpleVideoGenerator:
15
  def __init__(self):
16
+ """Initialize the video generator with basic components"""
17
+ self.setup_logging()
18
+ self.setup_workspace()
19
+ self.setup_themes()
20
+ self.load_font()
 
 
 
 
 
 
21
 
22
  def setup_logging(self):
23
+ """Configure basic logging"""
24
  logging.basicConfig(
25
  level=logging.INFO,
26
+ format='%(asctime)s - %(levelname)s - %(message)s'
 
 
 
 
27
  )
28
  self.logger = logging.getLogger(__name__)
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def setup_workspace(self):
31
+ """Set up temporary directory for working files"""
32
  self.temp_dir = Path(tempfile.mkdtemp())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ def setup_themes(self):
35
+ """Define color themes for videos"""
36
+ self.themes = {
37
+ 'Professional': {
38
+ 'bg': (245, 245, 245),
39
+ 'text': (33, 33, 33),
40
+ 'accent': (0, 102, 204)
41
+ },
42
+ 'Creative': {
43
+ 'bg': (255, 240, 245),
44
+ 'text': (51, 51, 51),
45
+ 'accent': (255, 64, 129)
46
+ },
47
+ 'Educational': {
48
+ 'bg': (240, 249, 255),
49
+ 'text': (25, 25, 25),
50
+ 'accent': (0, 151, 167)
51
  }
52
+ }
53
 
54
+ def load_font(self):
55
+ """Load system font or fall back to default"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  try:
57
+ # Try to load Arial font
58
+ self.font = ImageFont.truetype("arial.ttf", 40)
59
+ except OSError:
60
+ try:
61
+ # Try system font on Linux
62
+ self.font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40)
63
+ except OSError:
64
+ # Fall back to default font
65
+ self.font = ImageFont.load_default()
66
+ self.logger.warning("Using default font - custom font loading failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ def create_frame(
69
  self,
70
  text: str,
71
  theme: dict,
72
  frame_number: int,
73
  total_frames: int,
74
+ size: Tuple[int, int] = (1280, 720)
 
75
  ) -> np.ndarray:
76
+ """Create a single video frame with text overlay"""
77
  try:
78
  # Create base frame
79
+ frame = np.full((size[1], size[0], 3), theme['bg'], dtype=np.uint8)
 
 
 
 
 
 
 
80
  img = Image.fromarray(frame)
81
+ draw = ImageDraw.Draw(img)
 
 
 
 
 
 
 
 
 
82
 
83
+ # Wrap text for better presentation
 
84
  wrapped_text = textwrap.fill(text, width=50)
85
+
86
  # Calculate text position
87
  text_bbox = draw.textbbox((0, 0), wrapped_text, font=self.font)
88
  text_width = text_bbox[2] - text_bbox[0]
89
  text_height = text_bbox[3] - text_bbox[1]
90
  text_x = (size[0] - text_width) // 2
91
+ text_y = (size[1] - text_height) // 2 - 50 # Slightly above center
92
 
93
  # Draw text background
94
  padding = 20
 
99
  text_x + text_width + padding,
100
  text_y + text_height + padding
101
  ],
102
+ fill=theme['bg'],
103
+ outline=theme['accent']
104
  )
105
 
106
  # Draw text
107
  draw.text(
108
  (text_x, text_y),
109
  wrapped_text,
110
+ fill=theme['text'],
111
  font=self.font
112
  )
113
 
114
+ # Draw progress bar
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  progress = frame_number / total_frames
116
  bar_width = int(size[0] * 0.8) # 80% of screen width
117
  bar_height = 6
118
  x_offset = (size[0] - bar_width) // 2
119
  y_position = size[1] - 40
120
 
121
+ # Background bar
122
  draw.rectangle(
123
  [x_offset, y_position, x_offset + bar_width, y_position + bar_height],
124
+ fill=(200, 200, 200)
125
  )
126
 
127
+ # Progress bar
128
  progress_width = int(bar_width * progress)
129
+ draw.rectangle(
130
+ [x_offset, y_position, x_offset + progress_width, y_position + bar_height],
131
+ fill=theme['accent']
132
+ )
 
 
133
 
134
+ return np.array(img)
 
 
 
 
 
 
135
 
136
  except Exception as e:
137
+ self.logger.error(f"Frame creation failed: {str(e)}")
138
+ return np.zeros((size[1], size[0], 3), dtype=np.uint8)
139
 
140
+ def generate_audio(self, text: str) -> str:
141
+ """Generate audio from text using gTTS"""
142
  try:
143
+ audio_path = str(self.temp_dir / "audio.mp3")
144
+ tts = gTTS(text=text, lang='en', slow=False)
145
+ tts.save(audio_path)
146
+ return audio_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  except Exception as e:
148
+ self.logger.error(f"Audio generation failed: {str(e)}")
149
+ return None
 
150
 
151
  def create_video(
152
  self,
153
+ text: str,
154
  style: str,
155
  duration: int,
156
  output_path: str
157
+ ) -> Optional[str]:
158
+ """Create a video with text overlay and audio"""
159
  try:
160
+ # Generate audio first to get timing
161
+ audio_path = self.generate_audio(text)
162
+ if not audio_path:
163
+ raise Exception("Audio generation failed")
164
+
165
+ # Create frames
 
166
  frames = []
167
  fps = 30
168
  total_frames = int(duration * fps)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ for i in range(total_frames):
171
+ # Calculate current text segment
172
+ progress = i / total_frames
173
+ text_index = int(progress * len(text.split()))
174
+ current_text = " ".join(text.split()[:text_index + 1])
175
+
176
+ # Create frame
177
+ frame = self.create_frame(
178
+ current_text,
179
+ self.themes[style],
180
+ i,
181
+ total_frames
182
+ )
183
+ frames.append(frame)
184
+
185
+ # Create video
186
  video = ImageSequenceClip(frames, fps=fps)
187
+
188
+ # Add audio
189
+ audio = AudioFileClip(audio_path)
190
  video = video.set_audio(audio)
 
 
 
 
 
 
 
 
191
 
192
+ # Write video file
193
  video.write_videofile(
194
  output_path,
195
  fps=fps,
196
  codec='libx264',
197
+ audio_codec='aac'
 
 
198
  )
199
 
200
  return output_path
201
 
202
  except Exception as e:
203
  self.logger.error(f"Video creation failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  return None
205
 
206
  def cleanup(self):
207
+ """Clean up temporary files"""
208
  try:
209
+ import shutil
210
+ shutil.rmtree(self.temp_dir)
 
 
 
 
 
 
 
 
 
 
211
  except Exception as e:
212
  self.logger.error(f"Cleanup failed: {str(e)}")
213
 
214
+ # Streamlit UI
215
+ def main():
216
+ st.title("Simple Video Generator")
217
+ st.write("Create videos with text-to-speech and animations")
218
+
219
+ # Input fields
220
+ text = st.text_area("Enter your text", height=100)
221
+
222
+ col1, col2 = st.columns(2)
223
+ with col1:
224
+ style = st.selectbox(
225
+ "Choose style",
226
+ options=['Professional', 'Creative', 'Educational']
227
+ )
228
+
229
+ with col2:
230
+ duration = st.slider(
231
+ "Video duration (seconds)",
232
+ min_value=5,
233
+ max_value=60,
234
+ value=30
235
+ )
236
 
237
+ if st.button("Generate Video"):
238
+ if not text:
239
+ st.error("Please enter some text.")
240
+ return
241
 
242
+ try:
243
+ with st.spinner("Generating video..."):
244
+ generator = SimpleVideoGenerator()
245
+ output_path = "generated_video.mp4"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
+ result = generator.create_video(
248
+ text,
249
+ style,
250
+ duration,
251
+ output_path
252
  )
253
 
254
+ if result:
255
+ st.success("Video generated successfully!")
256
+
257
+ # Provide download button
258
+ with open(output_path, 'rb') as f:
259
+ st.download_button(
260
+ label="Download Video",
261
+ data=f.read(),
262
+ file_name=output_path,
263
+ mime="video/mp4"
 
 
 
 
 
 
 
 
 
 
264
  )
265
+ else:
266
+ st.error("Failed to generate video. Please try again.")
267
+
268
+ generator.cleanup()
269
+
270
+ except Exception as e:
271
+ st.error(f"An error occurred: {str(e)}")
 
 
 
 
 
 
 
 
272
 
273
  if __name__ == "__main__":
274
+ main()