File size: 7,429 Bytes
9b70717
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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)}")