Spaces:
Running
Running
import time | |
import os | |
import tempfile | |
import zipfile | |
import platform | |
import subprocess | |
import logging | |
from pathlib import Path | |
from moviepy.editor import (AudioFileClip, CompositeVideoClip, CompositeAudioClip, ImageClip, | |
TextClip, VideoFileClip) | |
from moviepy.audio.fx.audio_loop import audio_loop | |
from moviepy.audio.fx.audio_normalize import audio_normalize | |
import requests | |
logger = logging.getLogger(__name__) | |
def download_file(url, filename): | |
with open(filename, 'wb') as f: | |
headers = { | |
"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" | |
} | |
response = requests.get(url, headers=headers) | |
f.write(response.content) | |
def search_program(program_name): | |
try: | |
search_cmd = "where" if platform.system() == "Windows" else "which" | |
return subprocess.check_output([search_cmd, program_name]).decode().strip() | |
except subprocess.CalledProcessError: | |
return None | |
def get_program_path(program_name): | |
program_path = search_program(program_name) | |
return program_path | |
def get_output_media(audio_file_path, timed_captions, background_video_data, video_server): | |
"""Generate final video with audio and captions | |
Args: | |
audio_file_path (str): Path to audio file | |
timed_captions (list): List of timed captions | |
background_video_data (list): List of background video data | |
video_server (str): Video server URL | |
Returns: | |
str: Path to output video file | |
Raises: | |
Exception: If video rendering fails | |
""" | |
OUTPUT_FILE_NAME = "rendered_video.mp4" | |
from utility.conf import IMAGEMAGICK_BINARY | |
from moviepy.config import change_settings | |
try: | |
# Validate input files | |
if not Path(audio_file_path).exists(): | |
raise FileNotFoundError(f"Audio file not found at {audio_file_path}") | |
try: | |
change_settings({"IMAGEMAGICK_BINARY": IMAGEMAGICK_BINARY}) | |
logger.info(f"Using ImageMagick from: {IMAGEMAGICK_BINARY}") | |
except Exception as e: | |
logger.error(f"Error configuring ImageMagick: {str(e)}") | |
raise Exception(f"ImageMagick configuration failed: {str(e)}") | |
except Exception as e: | |
logger.error(f"Error in initial setup: {str(e)}") | |
raise Exception(f"Initial setup failed: {str(e)}") | |
visual_clips = [] | |
for (t1, t2), video_url in background_video_data: | |
try: | |
# Download the video file | |
video_filename = tempfile.NamedTemporaryFile(delete=False).name | |
logger.info(f"Downloading video from {video_url}") | |
download_file(video_url, video_filename) | |
if not Path(video_filename).exists(): | |
raise FileNotFoundError(f"Failed to download video from {video_url}") | |
# Create VideoFileClip from the downloaded file | |
video_clip = VideoFileClip(video_filename) | |
if video_clip is None: | |
raise ValueError(f"Failed to create video clip from {video_filename}") | |
video_clip = video_clip.set_start(t1) | |
video_clip = video_clip.set_end(t2) | |
visual_clips.append(video_clip) | |
logger.info(f"Added video clip from {video_url} ({t1}-{t2}s)") | |
except Exception as e: | |
logger.error(f"Error processing video {video_url}: {str(e)}") | |
raise Exception(f"Failed to process video {video_url}: {str(e)}") | |
audio_clips = [] | |
try: | |
# Verify audio file exists and is valid | |
if not os.path.exists(audio_file_path): | |
raise FileNotFoundError(f"Audio file not found: {audio_file_path}") | |
audio_file_clip = AudioFileClip(audio_file_path) | |
if audio_file_clip is None: | |
raise ValueError(f"Failed to create audio clip from {audio_file_path}") | |
# Normalize audio volume | |
audio_file_clip = audio_normalize(audio_file_clip) | |
# Verify audio duration | |
if audio_file_clip.duration <= 0: | |
raise ValueError("Audio file has zero or negative duration") | |
audio_clips.append(audio_file_clip) | |
logger.info(f"Added audio clip from {audio_file_path} (duration: {audio_file_clip.duration:.2f}s)") | |
except Exception as e: | |
logger.error(f"Error processing audio: {str(e)}") | |
raise Exception(f"Failed to process audio: {str(e)}") | |
for (t1, t2), text in timed_captions: | |
try: | |
# Updated caption style: changed font, fontsize, and position. | |
text_clip = TextClip( | |
txt=text, | |
fontsize=70, | |
font="Arial-Bold", | |
color="white", | |
stroke_width=2, | |
stroke_color="black", | |
method="label" | |
) | |
# Set the text to appear at the bottom-center | |
text_clip = text_clip.set_start(t1).set_end(t2).set_position(('center','bottom')) | |
visual_clips.append(text_clip) | |
logger.info(f"Added text clip: {text} ({t1}-{t2}s)") | |
except Exception as e: | |
logger.error(f"Error creating text clip: {str(e)}") | |
raise Exception(f"Failed to create text clip: {str(e)}") | |
try: | |
if not visual_clips: | |
raise ValueError("No visual clips available for rendering") | |
video = CompositeVideoClip(visual_clips) | |
if audio_clips: | |
audio = CompositeAudioClip(audio_clips) | |
# Ensure video duration matches audio and update video with audio properly | |
if video.duration < audio.duration: | |
last_clip = visual_clips[-1] | |
extended_clip = last_clip.set_end(audio.duration) | |
visual_clips[-1] = extended_clip | |
video = CompositeVideoClip(visual_clips) | |
video = video.set_duration(audio.duration) | |
# Updated audio application using set_audio | |
video = video.set_audio(audio) | |
logger.info(f"Audio synchronized with video (duration: {video.duration:.2f}s)") | |
logger.info(f"Rendering final video to {OUTPUT_FILE_NAME}") | |
video.write_videofile(OUTPUT_FILE_NAME, codec='libx264', audio_codec='aac', fps=25, preset='veryfast') | |
# Clean up downloaded files | |
for (t1, t2), video_url in background_video_data: | |
video_filename = tempfile.NamedTemporaryFile(delete=False).name | |
if Path(video_filename).exists(): | |
os.remove(video_filename) | |
logger.info(f"Cleaned up temporary file: {video_filename}") | |
if not Path(OUTPUT_FILE_NAME).exists(): | |
raise FileNotFoundError(f"Failed to create output video at {OUTPUT_FILE_NAME}") | |
logger.info(f"Successfully rendered video at {OUTPUT_FILE_NAME}") | |
return OUTPUT_FILE_NAME | |
except Exception as e: | |
logger.error(f"Error rendering video: {str(e)}") | |
raise Exception(f"Video rendering failed: {str(e)}") | |