Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -21,43 +21,6 @@ from typing import Optional, List, Dict, Tuple
|
|
| 21 |
from bs4 import BeautifulSoup
|
| 22 |
import requests
|
| 23 |
from io import BytesIO
|
| 24 |
-
import docx
|
| 25 |
-
import PyPDF2
|
| 26 |
-
import pptx
|
| 27 |
-
import cv2
|
| 28 |
-
from PIL import ImageEnhance
|
| 29 |
-
|
| 30 |
-
class FileProcessor:
|
| 31 |
-
@staticmethod
|
| 32 |
-
def read_txt(file):
|
| 33 |
-
return file.read().decode('utf-8')
|
| 34 |
-
|
| 35 |
-
@staticmethod
|
| 36 |
-
def read_pdf(file):
|
| 37 |
-
pdf_reader = PyPDF2.PdfReader(file)
|
| 38 |
-
text = ""
|
| 39 |
-
for page in pdf_reader.pages:
|
| 40 |
-
text += page.extract_text() + "\n"
|
| 41 |
-
return text
|
| 42 |
-
|
| 43 |
-
@staticmethod
|
| 44 |
-
def read_docx(file):
|
| 45 |
-
doc = docx.Document(file)
|
| 46 |
-
text = ""
|
| 47 |
-
for para in doc.paragraphs:
|
| 48 |
-
text += para.text + "\n"
|
| 49 |
-
return text
|
| 50 |
-
|
| 51 |
-
@staticmethod
|
| 52 |
-
def read_pptx(file):
|
| 53 |
-
prs = pptx.Presentation(file)
|
| 54 |
-
text = ""
|
| 55 |
-
for slide in prs.slides:
|
| 56 |
-
for shape in slide.shapes:
|
| 57 |
-
if hasattr(shape, "text"):
|
| 58 |
-
text += shape.text + "\n"
|
| 59 |
-
return text
|
| 60 |
-
|
| 61 |
|
| 62 |
class ImageScraper:
|
| 63 |
def __init__(self):
|
|
@@ -833,28 +796,26 @@ class EnhancedVideoGenerator:
|
|
| 833 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 834 |
|
| 835 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 836 |
-
"""Create video with
|
| 837 |
try:
|
| 838 |
# Initialize progress tracking
|
| 839 |
progress_bar = st.progress(0)
|
| 840 |
status_text = st.empty()
|
| 841 |
|
| 842 |
-
#
|
| 843 |
-
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 844 |
-
|
| 845 |
-
# Validate inputs and paths
|
| 846 |
-
if not output_path:
|
| 847 |
-
raise ValueError("Output path cannot be empty")
|
| 848 |
if not selected_images:
|
| 849 |
-
raise ValueError("No images selected")
|
| 850 |
-
|
| 851 |
-
|
|
|
|
|
|
|
|
|
|
| 852 |
status_text.text("Creating voice-over...")
|
| 853 |
-
audio = self.
|
| 854 |
progress_bar.progress(20)
|
| 855 |
|
| 856 |
-
# Process images
|
| 857 |
-
status_text.text("Processing images
|
| 858 |
processed_images = []
|
| 859 |
|
| 860 |
for img_url in selected_images:
|
|
@@ -863,30 +824,19 @@ class EnhancedVideoGenerator:
|
|
| 863 |
response.raise_for_status()
|
| 864 |
img = Image.open(BytesIO(response.content))
|
| 865 |
img = img.convert('RGB')
|
| 866 |
-
|
| 867 |
-
# Apply image effects based on style
|
| 868 |
-
if style == "Creative":
|
| 869 |
-
# Add creative effects
|
| 870 |
-
enhancer = ImageEnhance.Contrast(img)
|
| 871 |
-
img = enhancer.enhance(1.2)
|
| 872 |
-
enhancer = ImageEnhance.Brightness(img)
|
| 873 |
-
img = enhancer.enhance(1.1)
|
| 874 |
-
elif style == "Professional":
|
| 875 |
-
# Add professional effects
|
| 876 |
-
enhancer = ImageEnhance.Sharpness(img)
|
| 877 |
-
img = enhancer.enhance(1.3)
|
| 878 |
-
|
| 879 |
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 880 |
processed_images.append(img)
|
| 881 |
-
|
| 882 |
except Exception as e:
|
| 883 |
print(f"Error processing image {img_url}: {e}")
|
| 884 |
continue
|
| 885 |
|
|
|
|
|
|
|
|
|
|
| 886 |
progress_bar.progress(40)
|
| 887 |
|
| 888 |
-
#
|
| 889 |
-
status_text.text("
|
| 890 |
frames = []
|
| 891 |
fps = 30
|
| 892 |
total_frames = int(duration * fps)
|
|
@@ -895,36 +845,24 @@ class EnhancedVideoGenerator:
|
|
| 895 |
# Convert images to numpy arrays
|
| 896 |
image_arrays = [np.array(img) for img in processed_images]
|
| 897 |
|
| 898 |
-
#
|
| 899 |
frame_count = 0
|
| 900 |
for idx, img_array in enumerate(image_arrays):
|
| 901 |
-
# Calculate frames
|
| 902 |
if idx == len(image_arrays) - 1:
|
| 903 |
n_frames = total_frames - frame_count
|
| 904 |
else:
|
| 905 |
n_frames = min(frames_per_image, total_frames - frame_count)
|
| 906 |
|
| 907 |
-
# Add
|
| 908 |
-
for
|
| 909 |
-
|
| 910 |
-
alpha = 1.0
|
| 911 |
-
if frame_idx < 15: # Fade in
|
| 912 |
-
alpha = frame_idx / 15
|
| 913 |
-
elif frame_idx > n_frames - 15: # Fade out
|
| 914 |
-
alpha = (n_frames - frame_idx) / 15
|
| 915 |
-
|
| 916 |
-
frame = img_array * alpha
|
| 917 |
-
frames.append(frame.astype(np.uint8))
|
| 918 |
frame_count += 1
|
| 919 |
-
|
| 920 |
-
# Update progress
|
| 921 |
-
progress = int(40 + (frame_count / total_frames * 30))
|
| 922 |
-
progress_bar.progress(progress)
|
| 923 |
|
| 924 |
-
# Add transition to next image
|
| 925 |
if idx < len(image_arrays) - 1:
|
| 926 |
next_img_array = image_arrays[idx + 1]
|
| 927 |
-
transition_frames = 15
|
| 928 |
for t in range(transition_frames):
|
| 929 |
if frame_count < total_frames:
|
| 930 |
alpha = t / transition_frames
|
|
@@ -941,7 +879,7 @@ class EnhancedVideoGenerator:
|
|
| 941 |
status_text.text("Compiling video...")
|
| 942 |
clip = ImageSequenceClip(frames, fps=fps)
|
| 943 |
|
| 944 |
-
# Add audio
|
| 945 |
audio_duration = audio.duration
|
| 946 |
video_duration = len(frames) / fps
|
| 947 |
|
|
@@ -952,37 +890,27 @@ class EnhancedVideoGenerator:
|
|
| 952 |
|
| 953 |
final_clip = clip.set_audio(audio)
|
| 954 |
|
| 955 |
-
#
|
| 956 |
-
|
| 957 |
-
|
| 958 |
-
|
|
|
|
|
|
|
| 959 |
|
|
|
|
|
|
|
| 960 |
try:
|
| 961 |
final_clip.write_videofile(
|
| 962 |
output_path,
|
| 963 |
fps=fps,
|
| 964 |
codec='libx264',
|
| 965 |
audio_codec='aac',
|
| 966 |
-
ffmpeg_params=['-pix_fmt', 'yuv420p'],
|
| 967 |
-
temp_audiofile=os.path.join(cache_dir, "temp-audio.m4a"),
|
| 968 |
verbose=False,
|
| 969 |
logger=None
|
| 970 |
)
|
| 971 |
except Exception as e:
|
| 972 |
-
|
| 973 |
-
status_text.text("Attempting error recovery...")
|
| 974 |
-
try:
|
| 975 |
-
# Try alternative codec settings
|
| 976 |
-
final_clip.write_videofile(
|
| 977 |
-
output_path,
|
| 978 |
-
fps=fps,
|
| 979 |
-
codec='libx264',
|
| 980 |
-
audio_codec='mp3',
|
| 981 |
-
verbose=False,
|
| 982 |
-
logger=None
|
| 983 |
-
)
|
| 984 |
-
except Exception as recovery_e:
|
| 985 |
-
raise RuntimeError(f"Video creation failed even with recovery attempt: {str(recovery_e)}")
|
| 986 |
|
| 987 |
progress_bar.progress(100)
|
| 988 |
status_text.text("Video generation complete!")
|
|
@@ -991,7 +919,7 @@ class EnhancedVideoGenerator:
|
|
| 991 |
|
| 992 |
except Exception as e:
|
| 993 |
error_msg = f"Video creation failed: {str(e)}"
|
| 994 |
-
print(error_msg)
|
| 995 |
raise RuntimeError(error_msg)
|
| 996 |
finally:
|
| 997 |
# Cleanup
|
|
@@ -1004,9 +932,9 @@ class EnhancedVideoGenerator:
|
|
| 1004 |
audio.close()
|
| 1005 |
except Exception as e:
|
| 1006 |
print(f"Cleanup error: {e}")
|
|
|
|
| 1007 |
|
| 1008 |
|
| 1009 |
-
|
| 1010 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 1011 |
"""Generate relevant visual assets based on script content"""
|
| 1012 |
try:
|
|
@@ -1119,7 +1047,7 @@ class EnhancedVideoGenerator:
|
|
| 1119 |
# Streamlit UI Class
|
| 1120 |
class VideoGeneratorUI:
|
| 1121 |
def __init__(self):
|
| 1122 |
-
self.generator =
|
| 1123 |
self.setup_ui()
|
| 1124 |
|
| 1125 |
def setup_ui(self):
|
|
@@ -1248,8 +1176,7 @@ class VideoGeneratorUI:
|
|
| 1248 |
)
|
| 1249 |
except Exception as e:
|
| 1250 |
print(f"Error displaying image: {e}")
|
| 1251 |
-
|
| 1252 |
-
|
| 1253 |
def show_video_settings(self, prompt: str, selected_images: List[str]):
|
| 1254 |
"""Show video generation settings and controls"""
|
| 1255 |
st.subheader("Video Settings")
|
|
@@ -1276,29 +1203,19 @@ class VideoGeneratorUI:
|
|
| 1276 |
return
|
| 1277 |
|
| 1278 |
try:
|
| 1279 |
-
|
| 1280 |
-
|
| 1281 |
-
|
| 1282 |
-
|
| 1283 |
-
# Create absolute output path
|
| 1284 |
-
output_path = os.path.abspath(os.path.join(
|
| 1285 |
-
self.generator.base_dir,
|
| 1286 |
-
output_filename
|
| 1287 |
-
))
|
| 1288 |
|
| 1289 |
-
|
| 1290 |
-
progress_message = st.empty()
|
| 1291 |
-
progress_message.text("🎥 Generating your video... This may take a few minutes.")
|
| 1292 |
-
|
| 1293 |
-
video_path = self.generator.generate_video(
|
| 1294 |
prompt,
|
| 1295 |
-
|
| 1296 |
duration,
|
| 1297 |
-
output_path
|
|
|
|
| 1298 |
)
|
| 1299 |
|
| 1300 |
if os.path.exists(video_path):
|
| 1301 |
-
progress_message.empty()
|
| 1302 |
st.success("✨ Video generated successfully!")
|
| 1303 |
|
| 1304 |
# Display video
|
|
@@ -1310,7 +1227,7 @@ class VideoGeneratorUI:
|
|
| 1310 |
st.download_button(
|
| 1311 |
label="⬇️ Download Video",
|
| 1312 |
data=video_bytes,
|
| 1313 |
-
file_name=
|
| 1314 |
mime="video/mp4"
|
| 1315 |
)
|
| 1316 |
else:
|
|
@@ -1320,308 +1237,61 @@ class VideoGeneratorUI:
|
|
| 1320 |
st.error(f"Error generating video: {str(e)}")
|
| 1321 |
print(f"Video generation error: {str(e)}") # For debugging
|
| 1322 |
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
-
|
| 1326 |
-
|
| 1327 |
-
|
| 1328 |
-
|
| 1329 |
-
# Ensure directories exist
|
| 1330 |
-
os.makedirs(self.base_dir, exist_ok=True)
|
| 1331 |
-
os.makedirs(self.temp_dir, exist_ok=True)
|
| 1332 |
-
|
| 1333 |
-
self.setup_resources()
|
| 1334 |
-
|
| 1335 |
-
def setup_resources(self):
|
| 1336 |
-
# Initialize font
|
| 1337 |
-
try:
|
| 1338 |
-
font_options = [
|
| 1339 |
-
"arial.ttf",
|
| 1340 |
-
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
| 1341 |
-
"/System/Library/Fonts/Helvetica.ttc"
|
| 1342 |
-
]
|
| 1343 |
-
for font_path in font_options:
|
| 1344 |
-
try:
|
| 1345 |
-
self.font = ImageFont.truetype(font_path, 40)
|
| 1346 |
-
break
|
| 1347 |
-
except OSError:
|
| 1348 |
-
continue
|
| 1349 |
-
else:
|
| 1350 |
-
self.font = ImageFont.load_default()
|
| 1351 |
-
except Exception as e:
|
| 1352 |
-
print(f"Font loading error: {e}")
|
| 1353 |
-
self.font = ImageFont.load_default()
|
| 1354 |
-
|
| 1355 |
-
def create_video_frame(self, image, text, frame_number, total_frames, size=(1920, 1080)):
|
| 1356 |
-
try:
|
| 1357 |
-
# Resize and pad image to maintain aspect ratio
|
| 1358 |
-
img_aspect = image.size[0] / image.size[1]
|
| 1359 |
-
target_aspect = size[0] / size[1]
|
| 1360 |
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
-
|
| 1366 |
-
new_height = int(new_width / img_aspect)
|
| 1367 |
|
| 1368 |
-
|
| 1369 |
-
|
| 1370 |
-
# Create new background
|
| 1371 |
-
frame = Image.new('RGB', size, (0, 0, 0))
|
| 1372 |
-
|
| 1373 |
-
# Paste resized image in center
|
| 1374 |
-
paste_x = (size[0] - new_width) // 2
|
| 1375 |
-
paste_y = (size[1] - new_height) // 2
|
| 1376 |
-
frame.paste(image, (paste_x, paste_y))
|
| 1377 |
-
|
| 1378 |
-
# Add text overlay
|
| 1379 |
-
draw = ImageDraw.Draw(frame)
|
| 1380 |
-
|
| 1381 |
-
# Text background
|
| 1382 |
-
text = textwrap.fill(text, width=50)
|
| 1383 |
-
text_bbox = draw.textbbox((0, 0), text, font=self.font)
|
| 1384 |
-
text_width = text_bbox[2] - text_bbox[0]
|
| 1385 |
-
text_height = text_bbox[3] - text_bbox[1]
|
| 1386 |
-
|
| 1387 |
-
text_x = (size[0] - text_width) // 2
|
| 1388 |
-
text_y = size[1] - text_height - 100
|
| 1389 |
-
|
| 1390 |
-
# Semi-transparent background
|
| 1391 |
-
padding = 20
|
| 1392 |
-
draw.rectangle(
|
| 1393 |
-
[
|
| 1394 |
-
text_x - padding,
|
| 1395 |
-
text_y - padding,
|
| 1396 |
-
text_x + text_width + padding,
|
| 1397 |
-
text_y + text_height + padding
|
| 1398 |
-
],
|
| 1399 |
-
fill=(0, 0, 0, 180)
|
| 1400 |
-
)
|
| 1401 |
-
|
| 1402 |
-
# Draw text
|
| 1403 |
-
draw.text((text_x, text_y), text, fill=(255, 255, 255), font=self.font)
|
| 1404 |
-
|
| 1405 |
-
# Add progress bar
|
| 1406 |
-
self.draw_progress_bar(draw, frame_number, total_frames, size)
|
| 1407 |
-
|
| 1408 |
-
return np.array(frame)
|
| 1409 |
-
|
| 1410 |
-
except Exception as e:
|
| 1411 |
-
print(f"Frame creation error: {e}")
|
| 1412 |
-
return np.zeros((*size, 3), dtype=np.uint8)
|
| 1413 |
-
|
| 1414 |
-
def draw_progress_bar(self, draw, frame_number, total_frames, size):
|
| 1415 |
-
progress = frame_number / total_frames
|
| 1416 |
-
bar_width = int(size[0] * 0.8)
|
| 1417 |
-
bar_height = 6
|
| 1418 |
-
x_offset = (size[0] - bar_width) // 2
|
| 1419 |
-
y_position = size[1] - 40
|
| 1420 |
-
|
| 1421 |
-
# Background bar
|
| 1422 |
-
draw.rectangle(
|
| 1423 |
-
[x_offset, y_position, x_offset + bar_width, y_position + bar_height],
|
| 1424 |
-
fill=(100, 100, 100, 160)
|
| 1425 |
-
)
|
| 1426 |
-
|
| 1427 |
-
# Progress bar
|
| 1428 |
-
progress_width = int(bar_width * progress)
|
| 1429 |
-
draw.rectangle(
|
| 1430 |
-
[x_offset, y_position, x_offset + progress_width, y_position + bar_height],
|
| 1431 |
-
fill=(255, 255, 255, 200)
|
| 1432 |
-
)
|
| 1433 |
-
|
| 1434 |
-
def generate_video(self, script: str, images: List[str], duration: int, output_path: str) -> str:
|
| 1435 |
-
if not script or not images:
|
| 1436 |
-
raise ValueError("Script and images are required")
|
| 1437 |
-
|
| 1438 |
-
if not output_path:
|
| 1439 |
-
# Generate default output path if none provided
|
| 1440 |
-
timestamp = int(time.time())
|
| 1441 |
-
output_path = os.path.join(self.base_dir, f"video_{timestamp}.mp4")
|
| 1442 |
-
|
| 1443 |
-
# Ensure output directory exists
|
| 1444 |
-
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
| 1445 |
-
|
| 1446 |
-
try:
|
| 1447 |
-
# Process images
|
| 1448 |
-
processed_images = []
|
| 1449 |
-
for img_url in images:
|
| 1450 |
-
try:
|
| 1451 |
-
response = requests.get(img_url)
|
| 1452 |
-
img = Image.open(BytesIO(response.content)).convert('RGB')
|
| 1453 |
-
processed_images.append(img)
|
| 1454 |
-
except Exception as e:
|
| 1455 |
-
print(f"Image processing error: {e}")
|
| 1456 |
-
continue
|
| 1457 |
-
|
| 1458 |
-
if not processed_images:
|
| 1459 |
-
raise ValueError("No valid images to process")
|
| 1460 |
-
|
| 1461 |
-
# Generate frames
|
| 1462 |
-
fps = 30
|
| 1463 |
-
total_frames = duration * fps
|
| 1464 |
-
frames_per_image = total_frames // len(processed_images)
|
| 1465 |
-
|
| 1466 |
-
# Split script into sections
|
| 1467 |
-
words = script.split()
|
| 1468 |
-
words_per_image = len(words) // len(processed_images)
|
| 1469 |
-
|
| 1470 |
-
frames = []
|
| 1471 |
-
frame_count = 0
|
| 1472 |
-
|
| 1473 |
-
# Generate frames
|
| 1474 |
-
for idx, img in enumerate(processed_images):
|
| 1475 |
-
start_idx = idx * words_per_image
|
| 1476 |
-
end_idx = start_idx + words_per_image if idx < len(processed_images) - 1 else len(words)
|
| 1477 |
-
section_text = ' '.join(words[start_idx:end_idx])
|
| 1478 |
|
| 1479 |
-
|
| 1480 |
-
|
| 1481 |
-
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
| 1487 |
-
|
| 1488 |
-
|
| 1489 |
-
|
| 1490 |
-
|
| 1491 |
-
|
| 1492 |
-
|
| 1493 |
-
for t in range(15):
|
| 1494 |
-
if frame_count < total_frames:
|
| 1495 |
-
alpha = t / 15
|
| 1496 |
-
transition_frame = Image.blend(
|
| 1497 |
-
img,
|
| 1498 |
-
next_img,
|
| 1499 |
-
alpha
|
| 1500 |
-
)
|
| 1501 |
-
frame_img = self.create_video_frame(
|
| 1502 |
-
transition_frame,
|
| 1503 |
-
section_text,
|
| 1504 |
-
frame_count,
|
| 1505 |
-
total_frames
|
| 1506 |
-
)
|
| 1507 |
-
frames.append(frame_img)
|
| 1508 |
-
frame_count += 1
|
| 1509 |
-
|
| 1510 |
-
# Generate audio
|
| 1511 |
-
audio_path = os.path.join(self.temp_dir, "audio.mp3")
|
| 1512 |
-
tts = gTTS(text=script, lang='en')
|
| 1513 |
-
tts.save(audio_path)
|
| 1514 |
-
|
| 1515 |
-
# Create video
|
| 1516 |
-
clip = ImageSequenceClip(frames, fps=fps)
|
| 1517 |
-
audio_clip = AudioFileClip(audio_path)
|
| 1518 |
-
|
| 1519 |
-
# Adjust durations
|
| 1520 |
-
if audio_clip.duration < clip.duration:
|
| 1521 |
-
clip = clip.subclip(0, audio_clip.duration)
|
| 1522 |
-
else:
|
| 1523 |
-
audio_clip = audio_clip.subclip(0, clip.duration)
|
| 1524 |
-
|
| 1525 |
-
final_clip = clip.set_audio(audio_clip)
|
| 1526 |
-
|
| 1527 |
-
# Write video
|
| 1528 |
-
final_clip.write_videofile(
|
| 1529 |
-
output_path,
|
| 1530 |
-
fps=fps,
|
| 1531 |
-
codec='libx264',
|
| 1532 |
-
audio_codec='aac',
|
| 1533 |
-
ffmpeg_params=['-pix_fmt', 'yuv420p']
|
| 1534 |
-
)
|
| 1535 |
-
|
| 1536 |
-
return output_path
|
| 1537 |
-
|
| 1538 |
-
except Exception as e:
|
| 1539 |
-
print(f"Video generation error: {e}")
|
| 1540 |
-
raise
|
| 1541 |
-
finally:
|
| 1542 |
-
# Cleanup
|
| 1543 |
-
try:
|
| 1544 |
-
if 'clip' in locals():
|
| 1545 |
-
clip.close()
|
| 1546 |
-
if 'final_clip' in locals():
|
| 1547 |
-
final_clip.close()
|
| 1548 |
-
if 'audio_clip' in locals():
|
| 1549 |
-
audio_clip.close()
|
| 1550 |
-
except Exception as e:
|
| 1551 |
-
print(f"Cleanup error: {e}")
|
| 1552 |
-
|
| 1553 |
-
def cleanup(self):
|
| 1554 |
-
try:
|
| 1555 |
-
import shutil
|
| 1556 |
-
shutil.rmtree(self.temp_dir)
|
| 1557 |
-
except Exception as e:
|
| 1558 |
-
print(f"Cleanup error: {e}")
|
| 1559 |
-
|
| 1560 |
-
def create_ui():
|
| 1561 |
-
ui = VideoGeneratorUI()
|
| 1562 |
-
st.title("VaultGenix Video Generator")
|
| 1563 |
-
st.markdown("Create professional videos for your digital legacy management platform")
|
| 1564 |
-
|
| 1565 |
-
# File upload section
|
| 1566 |
-
st.subheader("Upload Content")
|
| 1567 |
-
uploaded_file = st.file_uploader(
|
| 1568 |
-
"Upload your content (PDF, DOCX, PPTX, or TXT)",
|
| 1569 |
-
type=['pdf', 'docx', 'pptx', 'txt']
|
| 1570 |
-
)
|
| 1571 |
-
|
| 1572 |
-
# Text input section
|
| 1573 |
-
script = ""
|
| 1574 |
-
if uploaded_file:
|
| 1575 |
-
try:
|
| 1576 |
-
file_processor = FileProcessor()
|
| 1577 |
-
if uploaded_file.type == "text/plain":
|
| 1578 |
-
script = file_processor.read_txt(uploaded_file)
|
| 1579 |
-
elif uploaded_file.type == "application/pdf":
|
| 1580 |
-
script = file_processor.read_pdf(uploaded_file)
|
| 1581 |
-
elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
| 1582 |
-
script = file_processor.read_docx(uploaded_file)
|
| 1583 |
-
elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
| 1584 |
-
script = file_processor.read_pptx(uploaded_file)
|
| 1585 |
-
except Exception as e:
|
| 1586 |
-
st.error(f"Error processing file: {str(e)}")
|
| 1587 |
-
|
| 1588 |
-
script = st.text_area("Enter or edit your video script", value=script, height=200)
|
| 1589 |
-
|
| 1590 |
-
if st.button("Generate Video") and script:
|
| 1591 |
-
try:
|
| 1592 |
-
# Initialize video generator
|
| 1593 |
-
generator = VideoGenerator()
|
| 1594 |
-
|
| 1595 |
-
# Get stock images (replace with your image selection logic)
|
| 1596 |
-
images = [
|
| 1597 |
-
"https://images.pexels.com/photos/60504/security-protection-anti-virus-software-60504.jpeg",
|
| 1598 |
-
"https://images.pexels.com/photos/5380642/pexels-photo-5380642.jpeg",
|
| 1599 |
-
"https://images.pexels.com/photos/2582937/pexels-photo-2582937.jpeg"
|
| 1600 |
-
]
|
| 1601 |
-
|
| 1602 |
-
# Generate video
|
| 1603 |
-
output_path = "output_video.mp4"
|
| 1604 |
-
with st.spinner("Generating video..."):
|
| 1605 |
-
video_path = generator.generate_video(script, images, 30, output_path)
|
| 1606 |
-
|
| 1607 |
-
# Display video
|
| 1608 |
-
if os.path.exists(video_path):
|
| 1609 |
-
st.success("Video generated successfully!")
|
| 1610 |
-
with open(video_path, 'rb') as video_file:
|
| 1611 |
video_bytes = video_file.read()
|
| 1612 |
st.video(video_bytes)
|
| 1613 |
|
| 1614 |
# Download button
|
| 1615 |
st.download_button(
|
| 1616 |
-
label="Download Video",
|
| 1617 |
data=video_bytes,
|
| 1618 |
-
file_name=
|
| 1619 |
mime="video/mp4"
|
| 1620 |
)
|
| 1621 |
-
|
| 1622 |
-
|
| 1623 |
-
|
| 1624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1625 |
|
| 1626 |
if __name__ == "__main__":
|
| 1627 |
-
|
|
|
|
| 21 |
from bs4 import BeautifulSoup
|
| 22 |
import requests
|
| 23 |
from io import BytesIO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
class ImageScraper:
|
| 26 |
def __init__(self):
|
|
|
|
| 796 |
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 797 |
|
| 798 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 799 |
+
"""Create video with selected images and improved error handling"""
|
| 800 |
try:
|
| 801 |
# Initialize progress tracking
|
| 802 |
progress_bar = st.progress(0)
|
| 803 |
status_text = st.empty()
|
| 804 |
|
| 805 |
+
# Validate inputs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 806 |
if not selected_images:
|
| 807 |
+
raise ValueError("No images selected. Please select at least one image.")
|
| 808 |
+
|
| 809 |
+
if not script.strip():
|
| 810 |
+
raise ValueError("Script cannot be empty.")
|
| 811 |
+
|
| 812 |
+
# Generate voice-over
|
| 813 |
status_text.text("Creating voice-over...")
|
| 814 |
+
audio = self.generate_fallback_audio(script) # Using fallback audio for reliability
|
| 815 |
progress_bar.progress(20)
|
| 816 |
|
| 817 |
+
# Process images
|
| 818 |
+
status_text.text("Processing images...")
|
| 819 |
processed_images = []
|
| 820 |
|
| 821 |
for img_url in selected_images:
|
|
|
|
| 824 |
response.raise_for_status()
|
| 825 |
img = Image.open(BytesIO(response.content))
|
| 826 |
img = img.convert('RGB')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
|
| 828 |
processed_images.append(img)
|
|
|
|
| 829 |
except Exception as e:
|
| 830 |
print(f"Error processing image {img_url}: {e}")
|
| 831 |
continue
|
| 832 |
|
| 833 |
+
if not processed_images:
|
| 834 |
+
raise ValueError("Failed to process any of the selected images.")
|
| 835 |
+
|
| 836 |
progress_bar.progress(40)
|
| 837 |
|
| 838 |
+
# Create frames
|
| 839 |
+
status_text.text("Generating frames...")
|
| 840 |
frames = []
|
| 841 |
fps = 30
|
| 842 |
total_frames = int(duration * fps)
|
|
|
|
| 845 |
# Convert images to numpy arrays
|
| 846 |
image_arrays = [np.array(img) for img in processed_images]
|
| 847 |
|
| 848 |
+
# Generate frames with transitions
|
| 849 |
frame_count = 0
|
| 850 |
for idx, img_array in enumerate(image_arrays):
|
| 851 |
+
# Calculate how many frames this image should appear
|
| 852 |
if idx == len(image_arrays) - 1:
|
| 853 |
n_frames = total_frames - frame_count
|
| 854 |
else:
|
| 855 |
n_frames = min(frames_per_image, total_frames - frame_count)
|
| 856 |
|
| 857 |
+
# Add main frames
|
| 858 |
+
for _ in range(n_frames):
|
| 859 |
+
frames.append(img_array)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
frame_count += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
|
| 862 |
+
# Add transition frames to next image
|
| 863 |
if idx < len(image_arrays) - 1:
|
| 864 |
next_img_array = image_arrays[idx + 1]
|
| 865 |
+
transition_frames = 15 # Number of transition frames
|
| 866 |
for t in range(transition_frames):
|
| 867 |
if frame_count < total_frames:
|
| 868 |
alpha = t / transition_frames
|
|
|
|
| 879 |
status_text.text("Compiling video...")
|
| 880 |
clip = ImageSequenceClip(frames, fps=fps)
|
| 881 |
|
| 882 |
+
# Add audio
|
| 883 |
audio_duration = audio.duration
|
| 884 |
video_duration = len(frames) / fps
|
| 885 |
|
|
|
|
| 890 |
|
| 891 |
final_clip = clip.set_audio(audio)
|
| 892 |
|
| 893 |
+
# Ensure output directory exists
|
| 894 |
+
output_dir = os.path.dirname(output_path)
|
| 895 |
+
if output_dir and not os.path.exists(output_dir):
|
| 896 |
+
os.makedirs(output_dir)
|
| 897 |
+
|
| 898 |
+
progress_bar.progress(90)
|
| 899 |
|
| 900 |
+
# Write video file
|
| 901 |
+
status_text.text("Saving video...")
|
| 902 |
try:
|
| 903 |
final_clip.write_videofile(
|
| 904 |
output_path,
|
| 905 |
fps=fps,
|
| 906 |
codec='libx264',
|
| 907 |
audio_codec='aac',
|
| 908 |
+
ffmpeg_params=['-pix_fmt', 'yuv420p'], # Ensure compatibility
|
|
|
|
| 909 |
verbose=False,
|
| 910 |
logger=None
|
| 911 |
)
|
| 912 |
except Exception as e:
|
| 913 |
+
raise RuntimeError(f"Failed to write video file: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 914 |
|
| 915 |
progress_bar.progress(100)
|
| 916 |
status_text.text("Video generation complete!")
|
|
|
|
| 919 |
|
| 920 |
except Exception as e:
|
| 921 |
error_msg = f"Video creation failed: {str(e)}"
|
| 922 |
+
print(error_msg) # For debugging
|
| 923 |
raise RuntimeError(error_msg)
|
| 924 |
finally:
|
| 925 |
# Cleanup
|
|
|
|
| 932 |
audio.close()
|
| 933 |
except Exception as e:
|
| 934 |
print(f"Cleanup error: {e}")
|
| 935 |
+
|
| 936 |
|
| 937 |
|
|
|
|
| 938 |
def generate_visual_assets(self, script: str, style: str) -> List[Dict]:
|
| 939 |
"""Generate relevant visual assets based on script content"""
|
| 940 |
try:
|
|
|
|
| 1047 |
# Streamlit UI Class
|
| 1048 |
class VideoGeneratorUI:
|
| 1049 |
def __init__(self):
|
| 1050 |
+
self.generator = EnhancedVideoGenerator()
|
| 1051 |
self.setup_ui()
|
| 1052 |
|
| 1053 |
def setup_ui(self):
|
|
|
|
| 1176 |
)
|
| 1177 |
except Exception as e:
|
| 1178 |
print(f"Error displaying image: {e}")
|
| 1179 |
+
|
|
|
|
| 1180 |
def show_video_settings(self, prompt: str, selected_images: List[str]):
|
| 1181 |
"""Show video generation settings and controls"""
|
| 1182 |
st.subheader("Video Settings")
|
|
|
|
| 1203 |
return
|
| 1204 |
|
| 1205 |
try:
|
| 1206 |
+
output_dir = "temp_videos"
|
| 1207 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 1208 |
+
output_path = os.path.join(output_dir, f"vaultgenix_video_{int(time.time())}.mp4")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1209 |
|
| 1210 |
+
video_path = self.generator.create_video(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1211 |
prompt,
|
| 1212 |
+
style,
|
| 1213 |
duration,
|
| 1214 |
+
output_path,
|
| 1215 |
+
selected_images
|
| 1216 |
)
|
| 1217 |
|
| 1218 |
if os.path.exists(video_path):
|
|
|
|
| 1219 |
st.success("✨ Video generated successfully!")
|
| 1220 |
|
| 1221 |
# Display video
|
|
|
|
| 1227 |
st.download_button(
|
| 1228 |
label="⬇️ Download Video",
|
| 1229 |
data=video_bytes,
|
| 1230 |
+
file_name=os.path.basename(video_path),
|
| 1231 |
mime="video/mp4"
|
| 1232 |
)
|
| 1233 |
else:
|
|
|
|
| 1237 |
st.error(f"Error generating video: {str(e)}")
|
| 1238 |
print(f"Video generation error: {str(e)}") # For debugging
|
| 1239 |
|
| 1240 |
+
def generate_video(self, prompt: str, style: str, duration: int, selected_images: List[str]):
|
| 1241 |
+
"""Handle video generation with improved error handling"""
|
| 1242 |
+
if not selected_images:
|
| 1243 |
+
st.error("Please select at least one image before generating the video.")
|
| 1244 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1245 |
|
| 1246 |
+
with st.spinner("🎥 Generating your video..."):
|
| 1247 |
+
try:
|
| 1248 |
+
# Create temp directory if it doesn't exist
|
| 1249 |
+
temp_dir = Path("temp_videos")
|
| 1250 |
+
temp_dir.mkdir(exist_ok=True)
|
|
|
|
| 1251 |
|
| 1252 |
+
# Generate unique output path
|
| 1253 |
+
output_path = str(temp_dir / f"vaultgenix_video_{int(time.time())}.mp4")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1254 |
|
| 1255 |
+
# Generate video
|
| 1256 |
+
video_path = self.generator.create_video(
|
| 1257 |
+
prompt,
|
| 1258 |
+
style,
|
| 1259 |
+
duration,
|
| 1260 |
+
output_path,
|
| 1261 |
+
selected_images
|
| 1262 |
+
)
|
| 1263 |
+
|
| 1264 |
+
if video_path and os.path.exists(video_path):
|
| 1265 |
+
st.success("✨ Video generated successfully!")
|
| 1266 |
+
|
| 1267 |
+
# Display video
|
| 1268 |
+
video_file = open(video_path, 'rb')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1269 |
video_bytes = video_file.read()
|
| 1270 |
st.video(video_bytes)
|
| 1271 |
|
| 1272 |
# Download button
|
| 1273 |
st.download_button(
|
| 1274 |
+
label="⬇️ Download Video",
|
| 1275 |
data=video_bytes,
|
| 1276 |
+
file_name=os.path.basename(video_path),
|
| 1277 |
mime="video/mp4"
|
| 1278 |
)
|
| 1279 |
+
|
| 1280 |
+
video_file.close()
|
| 1281 |
+
else:
|
| 1282 |
+
st.error("Video generation failed. Please try again.")
|
| 1283 |
+
|
| 1284 |
+
except Exception as e:
|
| 1285 |
+
st.error(f"Failed to generate video: {str(e)}")
|
| 1286 |
+
print(f"Video generation error: {str(e)}")
|
| 1287 |
+
|
| 1288 |
+
finally:
|
| 1289 |
+
# Cleanup temporary files
|
| 1290 |
+
try:
|
| 1291 |
+
if 'video_file' in locals():
|
| 1292 |
+
video_file.close()
|
| 1293 |
+
except Exception as e:
|
| 1294 |
+
print(f"Cleanup error: {e}")
|
| 1295 |
|
| 1296 |
if __name__ == "__main__":
|
| 1297 |
+
ui = VideoGeneratorUI()
|