Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1273,6 +1273,302 @@ class VideoGeneratorUI:
|
|
| 1273 |
st.error(f"Error generating video: {str(e)}")
|
| 1274 |
print(f"Video generation error: {str(e)}") # For debugging
|
| 1275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1276 |
def generate_video(self, prompt: str, style: str, duration: int, selected_images: List[str]):
|
| 1277 |
"""Handle video generation with improved error handling"""
|
| 1278 |
if not selected_images:
|
|
|
|
| 1273 |
st.error(f"Error generating video: {str(e)}")
|
| 1274 |
print(f"Video generation error: {str(e)}") # For debugging
|
| 1275 |
|
| 1276 |
+
class VideoGenerator:
|
| 1277 |
+
def __init__(self):
|
| 1278 |
+
self.temp_dir = Path(tempfile.mkdtemp())
|
| 1279 |
+
self.setup_resources()
|
| 1280 |
+
|
| 1281 |
+
def setup_resources(self):
|
| 1282 |
+
# Initialize font
|
| 1283 |
+
try:
|
| 1284 |
+
font_options = [
|
| 1285 |
+
"arial.ttf",
|
| 1286 |
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
| 1287 |
+
"/System/Library/Fonts/Helvetica.ttc"
|
| 1288 |
+
]
|
| 1289 |
+
for font_path in font_options:
|
| 1290 |
+
try:
|
| 1291 |
+
self.font = ImageFont.truetype(font_path, 40)
|
| 1292 |
+
break
|
| 1293 |
+
except OSError:
|
| 1294 |
+
continue
|
| 1295 |
+
else:
|
| 1296 |
+
self.font = ImageFont.load_default()
|
| 1297 |
+
except Exception as e:
|
| 1298 |
+
print(f"Font loading error: {e}")
|
| 1299 |
+
self.font = ImageFont.load_default()
|
| 1300 |
+
|
| 1301 |
+
def create_video_frame(self, image, text, frame_number, total_frames, size=(1920, 1080)):
|
| 1302 |
+
try:
|
| 1303 |
+
# Resize and pad image to maintain aspect ratio
|
| 1304 |
+
img_aspect = image.size[0] / image.size[1]
|
| 1305 |
+
target_aspect = size[0] / size[1]
|
| 1306 |
+
|
| 1307 |
+
if img_aspect > target_aspect:
|
| 1308 |
+
new_height = size[1]
|
| 1309 |
+
new_width = int(new_height * img_aspect)
|
| 1310 |
+
else:
|
| 1311 |
+
new_width = size[0]
|
| 1312 |
+
new_height = int(new_width / img_aspect)
|
| 1313 |
+
|
| 1314 |
+
image = image.resize((new_width, new_height), Image.LANCZOS)
|
| 1315 |
+
|
| 1316 |
+
# Create new background
|
| 1317 |
+
frame = Image.new('RGB', size, (0, 0, 0))
|
| 1318 |
+
|
| 1319 |
+
# Paste resized image in center
|
| 1320 |
+
paste_x = (size[0] - new_width) // 2
|
| 1321 |
+
paste_y = (size[1] - new_height) // 2
|
| 1322 |
+
frame.paste(image, (paste_x, paste_y))
|
| 1323 |
+
|
| 1324 |
+
# Add text overlay
|
| 1325 |
+
draw = ImageDraw.Draw(frame)
|
| 1326 |
+
|
| 1327 |
+
# Text background
|
| 1328 |
+
text = textwrap.fill(text, width=50)
|
| 1329 |
+
text_bbox = draw.textbbox((0, 0), text, font=self.font)
|
| 1330 |
+
text_width = text_bbox[2] - text_bbox[0]
|
| 1331 |
+
text_height = text_bbox[3] - text_bbox[1]
|
| 1332 |
+
|
| 1333 |
+
text_x = (size[0] - text_width) // 2
|
| 1334 |
+
text_y = size[1] - text_height - 100
|
| 1335 |
+
|
| 1336 |
+
# Semi-transparent background
|
| 1337 |
+
padding = 20
|
| 1338 |
+
draw.rectangle(
|
| 1339 |
+
[
|
| 1340 |
+
text_x - padding,
|
| 1341 |
+
text_y - padding,
|
| 1342 |
+
text_x + text_width + padding,
|
| 1343 |
+
text_y + text_height + padding
|
| 1344 |
+
],
|
| 1345 |
+
fill=(0, 0, 0, 180)
|
| 1346 |
+
)
|
| 1347 |
+
|
| 1348 |
+
# Draw text
|
| 1349 |
+
draw.text((text_x, text_y), text, fill=(255, 255, 255), font=self.font)
|
| 1350 |
+
|
| 1351 |
+
# Add progress bar
|
| 1352 |
+
self.draw_progress_bar(draw, frame_number, total_frames, size)
|
| 1353 |
+
|
| 1354 |
+
return np.array(frame)
|
| 1355 |
+
|
| 1356 |
+
except Exception as e:
|
| 1357 |
+
print(f"Frame creation error: {e}")
|
| 1358 |
+
return np.zeros((*size, 3), dtype=np.uint8)
|
| 1359 |
+
|
| 1360 |
+
def draw_progress_bar(self, draw, frame_number, total_frames, size):
|
| 1361 |
+
progress = frame_number / total_frames
|
| 1362 |
+
bar_width = int(size[0] * 0.8)
|
| 1363 |
+
bar_height = 6
|
| 1364 |
+
x_offset = (size[0] - bar_width) // 2
|
| 1365 |
+
y_position = size[1] - 40
|
| 1366 |
+
|
| 1367 |
+
# Background bar
|
| 1368 |
+
draw.rectangle(
|
| 1369 |
+
[x_offset, y_position, x_offset + bar_width, y_position + bar_height],
|
| 1370 |
+
fill=(100, 100, 100, 160)
|
| 1371 |
+
)
|
| 1372 |
+
|
| 1373 |
+
# Progress bar
|
| 1374 |
+
progress_width = int(bar_width * progress)
|
| 1375 |
+
draw.rectangle(
|
| 1376 |
+
[x_offset, y_position, x_offset + progress_width, y_position + bar_height],
|
| 1377 |
+
fill=(255, 255, 255, 200)
|
| 1378 |
+
)
|
| 1379 |
+
|
| 1380 |
+
def generate_video(self, script: str, images: List[str], duration: int, output_path: str) -> str:
|
| 1381 |
+
try:
|
| 1382 |
+
# Create temporary directory for processing
|
| 1383 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 1384 |
+
|
| 1385 |
+
# Process images
|
| 1386 |
+
processed_images = []
|
| 1387 |
+
for img_url in images:
|
| 1388 |
+
try:
|
| 1389 |
+
response = requests.get(img_url)
|
| 1390 |
+
img = Image.open(BytesIO(response.content)).convert('RGB')
|
| 1391 |
+
processed_images.append(img)
|
| 1392 |
+
except Exception as e:
|
| 1393 |
+
print(f"Image processing error: {e}")
|
| 1394 |
+
continue
|
| 1395 |
+
|
| 1396 |
+
if not processed_images:
|
| 1397 |
+
raise ValueError("No valid images to process")
|
| 1398 |
+
|
| 1399 |
+
# Generate frames
|
| 1400 |
+
fps = 30
|
| 1401 |
+
total_frames = duration * fps
|
| 1402 |
+
frames_per_image = total_frames // len(processed_images)
|
| 1403 |
+
|
| 1404 |
+
# Split script into sections
|
| 1405 |
+
words = script.split()
|
| 1406 |
+
words_per_image = len(words) // len(processed_images)
|
| 1407 |
+
|
| 1408 |
+
frames = []
|
| 1409 |
+
frame_count = 0
|
| 1410 |
+
|
| 1411 |
+
# Generate video frames
|
| 1412 |
+
for idx, img in enumerate(processed_images):
|
| 1413 |
+
# Get text section for this image
|
| 1414 |
+
start_idx = idx * words_per_image
|
| 1415 |
+
end_idx = start_idx + words_per_image if idx < len(processed_images) - 1 else len(words)
|
| 1416 |
+
section_text = ' '.join(words[start_idx:end_idx])
|
| 1417 |
+
|
| 1418 |
+
# Generate frames for this section
|
| 1419 |
+
for frame in range(frames_per_image):
|
| 1420 |
+
if frame_count < total_frames:
|
| 1421 |
+
frame_img = self.create_video_frame(
|
| 1422 |
+
img,
|
| 1423 |
+
section_text,
|
| 1424 |
+
frame_count,
|
| 1425 |
+
total_frames
|
| 1426 |
+
)
|
| 1427 |
+
frames.append(frame_img)
|
| 1428 |
+
frame_count += 1
|
| 1429 |
+
|
| 1430 |
+
# Add transition frames
|
| 1431 |
+
if idx < len(processed_images) - 1:
|
| 1432 |
+
next_img = processed_images[idx + 1]
|
| 1433 |
+
for t in range(15): # 15 frame transition
|
| 1434 |
+
if frame_count < total_frames:
|
| 1435 |
+
alpha = t / 15
|
| 1436 |
+
transition_frame = Image.blend(
|
| 1437 |
+
img,
|
| 1438 |
+
next_img,
|
| 1439 |
+
alpha
|
| 1440 |
+
)
|
| 1441 |
+
frame_img = self.create_video_frame(
|
| 1442 |
+
transition_frame,
|
| 1443 |
+
section_text,
|
| 1444 |
+
frame_count,
|
| 1445 |
+
total_frames
|
| 1446 |
+
)
|
| 1447 |
+
frames.append(frame_img)
|
| 1448 |
+
frame_count += 1
|
| 1449 |
+
|
| 1450 |
+
# Generate audio
|
| 1451 |
+
audio_path = self.temp_dir / "audio.mp3"
|
| 1452 |
+
tts = gTTS(text=script, lang='en')
|
| 1453 |
+
tts.save(str(audio_path))
|
| 1454 |
+
|
| 1455 |
+
# Create video
|
| 1456 |
+
clip = ImageSequenceClip(frames, fps=fps)
|
| 1457 |
+
audio_clip = AudioFileClip(str(audio_path))
|
| 1458 |
+
|
| 1459 |
+
# Adjust video length to match audio
|
| 1460 |
+
if audio_clip.duration < clip.duration:
|
| 1461 |
+
clip = clip.subclip(0, audio_clip.duration)
|
| 1462 |
+
else:
|
| 1463 |
+
audio_clip = audio_clip.subclip(0, clip.duration)
|
| 1464 |
+
|
| 1465 |
+
final_clip = clip.set_audio(audio_clip)
|
| 1466 |
+
|
| 1467 |
+
# Write video
|
| 1468 |
+
final_clip.write_videofile(
|
| 1469 |
+
output_path,
|
| 1470 |
+
fps=fps,
|
| 1471 |
+
codec='libx264',
|
| 1472 |
+
audio_codec='aac',
|
| 1473 |
+
ffmpeg_params=['-pix_fmt', 'yuv420p']
|
| 1474 |
+
)
|
| 1475 |
+
|
| 1476 |
+
return output_path
|
| 1477 |
+
|
| 1478 |
+
except Exception as e:
|
| 1479 |
+
print(f"Video generation error: {e}")
|
| 1480 |
+
raise
|
| 1481 |
+
finally:
|
| 1482 |
+
# Cleanup
|
| 1483 |
+
try:
|
| 1484 |
+
if 'clip' in locals():
|
| 1485 |
+
clip.close()
|
| 1486 |
+
if 'final_clip' in locals():
|
| 1487 |
+
final_clip.close()
|
| 1488 |
+
if 'audio_clip' in locals():
|
| 1489 |
+
audio_clip.close()
|
| 1490 |
+
except Exception as e:
|
| 1491 |
+
print(f"Cleanup error: {e}")
|
| 1492 |
+
|
| 1493 |
+
def cleanup(self):
|
| 1494 |
+
try:
|
| 1495 |
+
import shutil
|
| 1496 |
+
shutil.rmtree(self.temp_dir)
|
| 1497 |
+
except Exception as e:
|
| 1498 |
+
print(f"Cleanup error: {e}")
|
| 1499 |
+
|
| 1500 |
+
def create_ui():
|
| 1501 |
+
st.title("VaultGenix Video Generator")
|
| 1502 |
+
st.markdown("Create professional videos for your digital legacy management platform")
|
| 1503 |
+
|
| 1504 |
+
# File upload section
|
| 1505 |
+
st.subheader("Upload Content")
|
| 1506 |
+
uploaded_file = st.file_uploader(
|
| 1507 |
+
"Upload your content (PDF, DOCX, PPTX, or TXT)",
|
| 1508 |
+
type=['pdf', 'docx', 'pptx', 'txt']
|
| 1509 |
+
)
|
| 1510 |
+
|
| 1511 |
+
# Text input section
|
| 1512 |
+
script = ""
|
| 1513 |
+
if uploaded_file:
|
| 1514 |
+
try:
|
| 1515 |
+
file_processor = FileProcessor()
|
| 1516 |
+
if uploaded_file.type == "text/plain":
|
| 1517 |
+
script = file_processor.read_txt(uploaded_file)
|
| 1518 |
+
elif uploaded_file.type == "application/pdf":
|
| 1519 |
+
script = file_processor.read_pdf(uploaded_file)
|
| 1520 |
+
elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
| 1521 |
+
script = file_processor.read_docx(uploaded_file)
|
| 1522 |
+
elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
| 1523 |
+
script = file_processor.read_pptx(uploaded_file)
|
| 1524 |
+
except Exception as e:
|
| 1525 |
+
st.error(f"Error processing file: {str(e)}")
|
| 1526 |
+
|
| 1527 |
+
script = st.text_area("Enter or edit your video script", value=script, height=200)
|
| 1528 |
+
|
| 1529 |
+
if st.button("Generate Video") and script:
|
| 1530 |
+
try:
|
| 1531 |
+
# Initialize video generator
|
| 1532 |
+
generator = VideoGenerator()
|
| 1533 |
+
|
| 1534 |
+
# Get stock images (replace with your image selection logic)
|
| 1535 |
+
images = [
|
| 1536 |
+
"https://images.pexels.com/photos/60504/security-protection-anti-virus-software-60504.jpeg",
|
| 1537 |
+
"https://images.pexels.com/photos/5380642/pexels-photo-5380642.jpeg",
|
| 1538 |
+
"https://images.pexels.com/photos/2582937/pexels-photo-2582937.jpeg"
|
| 1539 |
+
]
|
| 1540 |
+
|
| 1541 |
+
# Generate video
|
| 1542 |
+
output_path = "output_video.mp4"
|
| 1543 |
+
with st.spinner("Generating video..."):
|
| 1544 |
+
video_path = generator.generate_video(script, images, 30, output_path)
|
| 1545 |
+
|
| 1546 |
+
# Display video
|
| 1547 |
+
if os.path.exists(video_path):
|
| 1548 |
+
st.success("Video generated successfully!")
|
| 1549 |
+
with open(video_path, 'rb') as video_file:
|
| 1550 |
+
video_bytes = video_file.read()
|
| 1551 |
+
st.video(video_bytes)
|
| 1552 |
+
|
| 1553 |
+
# Download button
|
| 1554 |
+
st.download_button(
|
| 1555 |
+
label="Download Video",
|
| 1556 |
+
data=video_bytes,
|
| 1557 |
+
file_name="vaultgenix_video.mp4",
|
| 1558 |
+
mime="video/mp4"
|
| 1559 |
+
)
|
| 1560 |
+
|
| 1561 |
+
except Exception as e:
|
| 1562 |
+
st.error(f"Error generating video: {str(e)}")
|
| 1563 |
+
print(f"Error details: {str(e)}")
|
| 1564 |
+
|
| 1565 |
+
if __name__ == "__main__":
|
| 1566 |
+
create_ui()
|
| 1567 |
+
|
| 1568 |
+
|
| 1569 |
+
|
| 1570 |
+
|
| 1571 |
+
|
| 1572 |
def generate_video(self, prompt: str, style: str, duration: int, selected_images: List[str]):
|
| 1573 |
"""Handle video generation with improved error handling"""
|
| 1574 |
if not selected_images:
|