Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -18,17 +18,59 @@ import random
|
|
| 18 |
import logging
|
| 19 |
import time
|
| 20 |
from typing import Optional, List, Dict, Tuple
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
class
|
| 23 |
def __init__(self):
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
try:
|
| 26 |
-
|
| 27 |
-
self.
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
except Exception as e:
|
| 33 |
logging.error(f"Initialization failed: {str(e)}")
|
| 34 |
raise RuntimeError("Failed to initialize video generator")
|
|
@@ -284,88 +326,77 @@ class EnhancedVideoGenerator:
|
|
| 284 |
self.logger.error(f"Voice-over generation failed: {str(e)}")
|
| 285 |
return AudioFileClip(duration=len(script.split()) * 0.3)
|
| 286 |
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
# Generate voice-over (40%)
|
| 300 |
-
status_text.text("Creating voice-over...")
|
| 301 |
-
audio = self.generate_voice_over(script)
|
| 302 |
-
progress_bar.progress(40)
|
| 303 |
-
|
| 304 |
-
# Create frames
|
| 305 |
-
fps = 30
|
| 306 |
-
total_frames = int(duration * fps)
|
| 307 |
-
frames = []
|
| 308 |
-
|
| 309 |
-
# Use smaller batch size for better memory management
|
| 310 |
-
batch_size = 10
|
| 311 |
-
num_batches = (total_frames + batch_size - 1) // batch_size
|
| 312 |
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
progress_bar.progress(100)
|
| 362 |
-
status_text.text("Video generation complete!")
|
| 363 |
-
return output_path
|
| 364 |
-
|
| 365 |
-
except Exception as e:
|
| 366 |
-
self.logger.error(f"Video creation failed: {str(e)}")
|
| 367 |
-
raise
|
| 368 |
|
|
|
|
| 369 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 370 |
"""Generate relevant visual assets based on script content"""
|
| 371 |
try:
|
|
@@ -504,42 +535,59 @@ class VideoGeneratorUI:
|
|
| 504 |
|
| 505 |
def setup_ui(self):
|
| 506 |
st.title("Enhanced Video Generator")
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
|
| 528 |
-
|
| 529 |
-
st.
|
| 530 |
-
|
| 531 |
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
except Exception as e:
|
| 542 |
-
st.error(f"Failed to generate video: {str(e)}")
|
| 543 |
|
| 544 |
if __name__ == "__main__":
|
| 545 |
ui = VideoGeneratorUI()
|
|
|
|
| 18 |
import logging
|
| 19 |
import time
|
| 20 |
from typing import Optional, List, Dict, Tuple
|
| 21 |
+
from bs4 import BeautifulSoup
|
| 22 |
+
import requests
|
| 23 |
+
from io import BytesIO
|
| 24 |
|
| 25 |
+
class ImageScraper:
|
| 26 |
def __init__(self):
|
| 27 |
+
self.headers = {
|
| 28 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
def scrape_pexels(self, query: str) -> List[str]:
|
| 32 |
+
urls = []
|
| 33 |
+
try:
|
| 34 |
+
url = f"https://www.pexels.com/search/{query.replace(' ', '%20')}/"
|
| 35 |
+
response = requests.get(url, headers=self.headers)
|
| 36 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 37 |
+
for img in soup.find_all('img', src=True):
|
| 38 |
+
if 'photos' in img['src'] and 'pexels.com' in img['src']:
|
| 39 |
+
urls.append(img['src'])
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"Pexels scraping error: {e}")
|
| 42 |
+
return urls
|
| 43 |
+
|
| 44 |
+
def scrape_unsplash(self, query: str) -> List[str]:
|
| 45 |
+
urls = []
|
| 46 |
try:
|
| 47 |
+
url = f"https://unsplash.com/s/photos/{query.replace(' ', '-')}"
|
| 48 |
+
response = requests.get(url, headers=self.headers)
|
| 49 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 50 |
+
for img in soup.find_all('img', src=True):
|
| 51 |
+
if 'images.unsplash.com' in img['src']:
|
| 52 |
+
urls.append(img['src'])
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"Unsplash scraping error: {e}")
|
| 55 |
+
return urls
|
| 56 |
+
|
| 57 |
+
def get_images(self, query: str, num_images: int = 15) -> List[str]:
|
| 58 |
+
all_urls = []
|
| 59 |
+
all_urls.extend(self.scrape_pexels(query))
|
| 60 |
+
all_urls.extend(self.scrape_unsplash(query))
|
| 61 |
+
|
| 62 |
+
# Remove duplicates and limit to num_images
|
| 63 |
+
return list(set(all_urls))[:num_images]
|
| 64 |
+
|
| 65 |
+
class EnhancedVideoGenerator:
|
| 66 |
+
def __init__(self):
|
| 67 |
+
self.setup_logging()
|
| 68 |
+
self.setup_device()
|
| 69 |
+
self.initialize_models()
|
| 70 |
+
self.setup_workspace()
|
| 71 |
+
self.load_assets()
|
| 72 |
+
self.setup_themes()
|
| 73 |
+
self.image_scraper = ImageScraper()
|
| 74 |
except Exception as e:
|
| 75 |
logging.error(f"Initialization failed: {str(e)}")
|
| 76 |
raise RuntimeError("Failed to initialize video generator")
|
|
|
|
| 326 |
self.logger.error(f"Voice-over generation failed: {str(e)}")
|
| 327 |
return AudioFileClip(duration=len(script.split()) * 0.3)
|
| 328 |
|
| 329 |
+
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 330 |
+
"""Create video with selected images"""
|
| 331 |
+
try:
|
| 332 |
+
# Progress bar
|
| 333 |
+
progress_bar = st.progress(0)
|
| 334 |
+
status_text = st.empty()
|
| 335 |
+
|
| 336 |
+
# Generate voice-over (20%)
|
| 337 |
+
status_text.text("Creating voice-over...")
|
| 338 |
+
audio = self.generate_voice_over(script)
|
| 339 |
+
progress_bar.progress(20)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
+
# Process selected images (40%)
|
| 342 |
+
status_text.text("Processing images...")
|
| 343 |
+
processed_images = []
|
| 344 |
+
for img_url in selected_images:
|
| 345 |
+
response = requests.get(img_url)
|
| 346 |
+
img = Image.open(BytesIO(response.content))
|
| 347 |
+
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 348 |
+
processed_images.append(np.array(img))
|
| 349 |
+
progress_bar.progress(40)
|
| 350 |
+
|
| 351 |
+
# Create frames with transitions
|
| 352 |
+
fps = 30
|
| 353 |
+
total_frames = int(duration * fps)
|
| 354 |
+
frames = []
|
| 355 |
+
|
| 356 |
+
status_text.text("Generating frames...")
|
| 357 |
+
frames_per_image = total_frames // len(processed_images)
|
| 358 |
+
|
| 359 |
+
for idx, img in enumerate(processed_images):
|
| 360 |
+
for _ in range(frames_per_image):
|
| 361 |
+
frames.append(img)
|
| 362 |
|
| 363 |
+
# Add transition frames
|
| 364 |
+
if idx < len(processed_images) - 1:
|
| 365 |
+
next_img = processed_images[idx + 1]
|
| 366 |
+
for alpha in np.linspace(0, 1, 15):
|
| 367 |
+
transition_frame = (1 - alpha) * img + alpha * next_img
|
| 368 |
+
frames.append(transition_frame.astype(np.uint8))
|
| 369 |
+
|
| 370 |
+
progress_bar.progress(70)
|
| 371 |
+
|
| 372 |
+
# Create video clip
|
| 373 |
+
status_text.text("Compiling video...")
|
| 374 |
+
video = ImageSequenceClip(frames, fps=fps)
|
| 375 |
+
video = video.set_audio(audio)
|
| 376 |
+
|
| 377 |
+
progress_bar.progress(90)
|
| 378 |
+
|
| 379 |
+
# Write final video
|
| 380 |
+
status_text.text("Saving video...")
|
| 381 |
+
video.write_videofile(
|
| 382 |
+
output_path,
|
| 383 |
+
fps=fps,
|
| 384 |
+
codec='libx264',
|
| 385 |
+
audio_codec='aac',
|
| 386 |
+
threads=4,
|
| 387 |
+
preset='ultrafast'
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
progress_bar.progress(100)
|
| 391 |
+
status_text.text("Video generation complete!")
|
| 392 |
+
|
| 393 |
+
return output_path
|
| 394 |
+
|
| 395 |
+
except Exception as e:
|
| 396 |
+
self.logger.error(f"Video creation failed: {str(e)}")
|
| 397 |
+
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
|
| 399 |
+
|
| 400 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 401 |
"""Generate relevant visual assets based on script content"""
|
| 402 |
try:
|
|
|
|
| 535 |
|
| 536 |
def setup_ui(self):
|
| 537 |
st.title("Enhanced Video Generator")
|
| 538 |
+
|
| 539 |
+
# Step 1: Input prompt
|
| 540 |
+
prompt = st.text_input("Enter your video topic/prompt")
|
| 541 |
+
|
| 542 |
+
if prompt:
|
| 543 |
+
# Step 2: Image Selection
|
| 544 |
+
st.subheader("Select Images for Your Video")
|
| 545 |
+
images = self.generator.image_scraper.get_images(prompt)
|
| 546 |
|
| 547 |
+
if not images:
|
| 548 |
+
st.warning("No images found. Try a different search term.")
|
| 549 |
+
return
|
| 550 |
+
|
| 551 |
+
selected_images = []
|
| 552 |
+
cols = st.columns(3)
|
| 553 |
+
for idx, img_url in enumerate(images):
|
| 554 |
+
with cols[idx % 3]:
|
| 555 |
+
try:
|
| 556 |
+
response = requests.get(img_url)
|
| 557 |
+
img = Image.open(BytesIO(response.content))
|
| 558 |
+
st.image(img, use_column_width=True)
|
| 559 |
+
if st.checkbox(f"Select Image {idx + 1}", key=f"img_{idx}"):
|
| 560 |
+
selected_images.append(img_url)
|
| 561 |
+
except:
|
| 562 |
+
continue
|
| 563 |
+
|
| 564 |
+
# Step 3: Video Generation (only show if images are selected)
|
| 565 |
+
if selected_images:
|
| 566 |
+
st.subheader("Video Generation Settings")
|
| 567 |
+
col1, col2 = st.columns(2)
|
| 568 |
+
with col1:
|
| 569 |
+
style = st.selectbox("Choose style", options=list(self.generator.themes.keys()))
|
| 570 |
+
with col2:
|
| 571 |
+
duration = st.slider("Video duration (seconds)", 10, 300, 60, 10)
|
| 572 |
+
|
| 573 |
+
if st.button("Generate Video"):
|
| 574 |
+
try:
|
| 575 |
+
output_path = os.path.join(os.getcwd(), f"generated_video_{int(time.time())}.mp4")
|
| 576 |
+
video_path = self.generator.create_video(prompt, style, duration, output_path, selected_images)
|
| 577 |
|
| 578 |
+
if os.path.exists(video_path):
|
| 579 |
+
st.success("Video generated successfully!")
|
| 580 |
+
st.video(video_path)
|
| 581 |
|
| 582 |
+
with open(video_path, 'rb') as video_file:
|
| 583 |
+
st.download_button(
|
| 584 |
+
"Download Video",
|
| 585 |
+
video_file.read(),
|
| 586 |
+
file_name=os.path.basename(video_path),
|
| 587 |
+
mime="video/mp4"
|
| 588 |
+
)
|
| 589 |
+
except Exception as e:
|
| 590 |
+
st.error(f"Failed to generate video: {str(e)}")
|
|
|
|
|
|
|
| 591 |
|
| 592 |
if __name__ == "__main__":
|
| 593 |
ui = VideoGeneratorUI()
|