File size: 11,305 Bytes
c7600a9
3e3c550
 
 
32c6d88
 
 
 
 
 
7d3e520
32c6d88
 
 
 
3e3c550
32c6d88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d3e520
3e3c550
 
 
 
7d3e520
3e3c550
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32c6d88
c7600a9
3e3c550
 
 
 
 
 
 
 
32c6d88
3e3c550
c7600a9
3e3c550
 
32c6d88
c7600a9
3e3c550
 
 
 
 
32c6d88
 
 
3e3c550
 
7d3e520
3e3c550
 
 
 
 
32c6d88
3e3c550
 
32c6d88
 
 
 
 
 
 
3e3c550
32c6d88
3e3c550
 
c7600a9
 
3e3c550
7d3e520
c7600a9
3e3c550
7d3e520
3e3c550
32c6d88
3e3c550
 
 
 
 
 
 
c7600a9
32c6d88
3e3c550
 
c7600a9
3e3c550
c7600a9
32c6d88
 
 
 
3e3c550
15daf82
 
c7600a9
3e3c550
 
 
32c6d88
3e3c550
 
 
 
 
32c6d88
3e3c550
 
 
 
 
32c6d88
3e3c550
 
 
c7600a9
3e3c550
 
32c6d88
3e3c550
32c6d88
 
 
 
 
3e3c550
 
 
 
 
 
 
 
 
 
32c6d88
c7600a9
3e3c550
 
 
15daf82
3e3c550
 
 
 
32c6d88
 
3e3c550
15daf82
0212e21
32c6d88
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
import gradio as gr
import os
import tempfile
import shutil
import random
import requests
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, ImageClip, TextClip
import numpy as np
from gtts import gTTS
import cv2

# Global configurations
PEXELS_API_KEY = 'BhJqbcdm9Vi90KqzXKAhnEHGsuFNv4irXuOjWtT761U49lRzo03qBGna'  # Replace with your Pexels API key
OUTPUT_VIDEO_FILENAME = "final_video.mp4"
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"
MAX_CLIPS = 10
TARGET_RESOLUTION = (1920, 1080)  # Default resolution
TEMP_FOLDER = None
CAPTION_COLOR = "#FFFFFF"

# Helper Functions
def generate_script(topic):
    """Generate a simple script based on the topic."""
    return f"[Title]\n{topic}\n[Narration]\nThis is a sample narration about {topic}."

def parse_script(script):
    """Parse the script into elements."""
    elements = []
    lines = script.strip().split('\n')
    for line in lines:
        if line.startswith('[Title]'):
            elements.append({'type': 'media', 'prompt': line[7:].strip()})
        elif line.startswith('[Narration]'):
            elements.append({'type': 'tts', 'text': line[11:].strip()})
    return elements

def search_pexels_videos(query, api_key):
    """Search for videos on Pexels."""
    url = f"https://api.pexels.com/videos/search?query={quote(query)}&per_page=1"
    headers = {"Authorization": api_key}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        data = response.json()
        if data['videos']:
            return data['videos'][0]['video_files'][0]['link']
    return None

def download_video(url, output_path):
    """Download a video from a URL."""
    response = requests.get(url, headers={"User-Agent": USER_AGENT}, stream=True)
    if response.status_code == 200:
        with open(output_path, 'wb') as f:
            shutil.copyfileobj(response.raw, f)
        return output_path
    return None

def generate_tts(text, lang='en'):
    """Generate TTS audio using gTTS."""
    tts = gTTS(text=text, lang=lang, slow=False)
    tts_path = os.path.join(TEMP_FOLDER, f"tts_{random.randint(0, 10000)}.mp3")
    tts.save(tts_path)
    return tts_path

def resize_media(media_path, target_resolution):
    """Resize media to match target resolution."""
    clip = VideoFileClip(media_path) if media_path.endswith(('.mp4', '.avi', '.mov')) else ImageClip(media_path)
    return clip.resize(target_resolution)

def apply_kenburns_effect(clip, target_resolution):
    """Apply a zoom/pan (Ken Burns) effect to an image clip."""
    w, h = target_resolution
    clip = clip.resize(height=h * 1.2).crop(x_center=w/2, y_center=h/2, width=w, height=h)
    return clip.fx(vfx.zoom_in, 0.1)

def create_clip(media_path, asset_type, tts_path, duration, effects, narration_text, segment_index):
    """Create a video clip with media and narration."""
    if asset_type == 'video':
        clip = VideoFileClip(media_path).subclip(0, min(duration, VideoFileClip(media_path).duration))
    else:
        clip = ImageClip(media_path, duration=duration)
    
    clip = resize_media(media_path, TARGET_RESOLUTION)
    if effects == 'fade-in':
        clip = clip.crossfadein(1.0)
    
    audio = AudioFileClip(tts_path)
    clip = clip.set_audio(audio.set_duration(duration))
    
    if CAPTION_COLOR != "transparent":
        txt_clip = TextClip(narration_text, fontsize=45, color=CAPTION_COLOR, font="Arial", stroke_color="#000000", stroke_width=2)
        txt_clip = txt_clip.set_position('bottom').set_duration(duration)
        clip = CompositeVideoClip([clip, txt_clip])
    
    return clip

def generate_media(prompt, current_index, total_segments):
    """Generate media (placeholder for actual generation)."""
    media_path = os.path.join(TEMP_FOLDER, f"media_{current_index}.jpg")
    with open(media_path, 'wb') as f:
        f.write(requests.get("https://via.placeholder.com/1920x1080").content)  # Placeholder image
    return {'path': media_path, 'asset_type': 'image'}

def process_script(topic, script_input):
    """Process the topic or script and return updates for the UI."""
    if script_input.strip():
        raw_script = script_input
    else:
        raw_script = generate_script(topic)
        if not raw_script:
            return "Failed to generate script", 0, [], [], [], []

    elements = parse_script(raw_script)
    paired_elements = [(elements[i], elements[i + 1]) for i in range(0, len(elements) - 1, 2)]
    num_clips = min(len(paired_elements), MAX_CLIPS)

    accordion_updates = []
    prompt_updates = []
    narration_updates = []
    media_updates = []
    for i in range(MAX_CLIPS):
        if i < num_clips:
            media_elem, tts_elem = paired_elements[i]
            accordion_updates.append(gr.update(visible=True, label=f"Clip {i+1}: {media_elem['prompt'][:20]}..."))
            prompt_updates.append(gr.update(value=media_elem['prompt']))
            narration_updates.append(gr.update(value=tts_elem['text']))
            media_updates.append(gr.update(value=None))
        else:
            accordion_updates.append(gr.update(visible=False))
            prompt_updates.append(gr.update(value=""))
            narration_updates.append(gr.update(value=""))
            media_updates.append(gr.update(value=None))

    return raw_script, num_clips, accordion_updates, prompt_updates, narration_updates, media_updates

def generate_video_full(resolution, render_speed, video_clip_percent, zoom_pan_effect, 
                        bgm_upload, bgm_volume, subtitles_enabled, num_clips, *clip_inputs):
    """Generate the video using all settings and edited clip data."""
    global TARGET_RESOLUTION, CAPTION_COLOR, TEMP_FOLDER
    
    TARGET_RESOLUTION = (1080, 1920) if resolution == "Short (1080x1920)" else (1920, 1080)
    CAPTION_COLOR = "#FFFFFF" if subtitles_enabled else "transparent"
    TEMP_FOLDER = tempfile.mkdtemp()
    
    clips_data = []
    for i in range(num_clips):
        idx = i * 3
        clips_data.append({
            'visual_prompt': clip_inputs[idx],
            'narration': clip_inputs[idx + 1],
            'custom_media': clip_inputs[idx + 2]
        })

    clips = []
    for idx, clip_data in enumerate(clips_data):
        if clip_data['custom_media']:
            media_path = clip_data['custom_media']
            asset_type = 'video' if media_path.endswith(('.mp4', '.avi', '.mov')) else 'image'
        else:
            media_asset = generate_media(clip_data['visual_prompt'], idx, num_clips)
            media_path = media_asset['path']
            asset_type = media_asset['asset_type']
            if random.random() < (video_clip_percent / 100):
                video_url = search_pexels_videos(clip_data['visual_prompt'], PEXELS_API_KEY)
                if video_url:
                    media_path = download_video(video_url, os.path.join(TEMP_FOLDER, f"video_{idx}.mp4"))
                    asset_type = 'video'

        tts_path = generate_tts(clip_data['narration'])
        duration = max(3, len(clip_data['narration'].split()) * 0.5)
        clip = create_clip(media_path, asset_type, tts_path, duration, 'fade-in', clip_data['narration'], idx)
        if clip and zoom_pan_effect and asset_type == 'image':
            clip = apply_kenburns_effect(clip, TARGET_RESOLUTION)
        if clip:
            clips.append(clip)

    if not clips:
        shutil.rmtree(TEMP_FOLDER)
        return None, None

    final_video = concatenate_videoclips(clips, method="compose")
    
    if bgm_upload:
        bg_music = AudioFileClip(bgm_upload).volumex(bgm_volume)
        if bg_music.duration < final_video.duration:
            bg_music = bg_music.loop(duration=final_video.duration)
        else:
            bg_music = bg_music.subclip(0, final_video.duration)
        final_video = final_video.set_audio(CompositeVideoClip([final_video.audio, bg_music]))

    output_path = OUTPUT_VIDEO_FILENAME
    final_video.write_videofile(output_path, codec='libx264', fps=24, preset=render_speed)
    shutil.rmtree(TEMP_FOLDER)

    return output_path, output_path

# Gradio Interface
with gr.Blocks(title="Video Generator") as demo:
    gr.Markdown("# Video Generator")
    gr.Markdown("Create custom videos with script, clips, and settings!")
    
    with gr.Row():
        # Column 1: Content Input & Script Generation
        with gr.Column(scale=1):
            gr.Markdown("### 1. Content Input")
            topic_input = gr.Textbox(label="Topic", placeholder="e.g., Funny Cat Facts")
            script_input = gr.Textbox(label="Or Paste Full Script", lines=10, placeholder="[Title]\nNarration...")
            generate_button = gr.Button("Generate Script & Load Clips")
            script_display = gr.Textbox(label="Generated Script", interactive=False, visible=False)
        
        # Column 2: Clip Editor
        with gr.Column(scale=2):
            gr.Markdown("### 2. Edit Clips")
            with gr.Column():
                clip_accordions = []
                for i in range(MAX_CLIPS):
                    with gr.Accordion(f"Clip {i+1}", visible=False) as acc:
                        visual_prompt = gr.Textbox(label="Visual Prompt")
                        narration = gr.Textbox(label="Narration", lines=3)
                        custom_media = gr.File(label="Upload Custom Media")
                        clip_accordions.append((acc, visual_prompt, narration, custom_media))
        
        # Column 3: Settings & Output
        with gr.Column(scale=1):
            gr.Markdown("### 3. Video Settings")
            resolution = gr.Radio(["Short (1080x1920)", "Full HD (1920x1080)"], label="Resolution", value="Full HD (1920x1080)")
            render_speed = gr.Dropdown(["ultrafast", "fast", "medium", "slow"], label="Render Speed", value="fast")
            video_clip_percent = gr.Slider(0, 100, value=25, label="Video Clip Percentage")
            zoom_pan_effect = gr.Checkbox(label="Add Zoom/Pan Effect", value=True)
            bgm_upload = gr.Audio(label="Upload Background Music", type="filepath")
            bgm_volume = gr.Slider(0.0, 1.0, value=0.15, label="BGM Volume")
            subtitles_enabled = gr.Checkbox(label="Enable Subtitles", value=True)
            generate_video_button = gr.Button("Generate Video")
            gr.Markdown("### 4. Output")
            output_video = gr.Video(label="Generated Video")
            download_button = gr.File(label="Download Video")

    num_clips_state = gr.State(value=0)

    generate_button.click(
        fn=process_script,
        inputs=[topic_input, script_input],
        outputs=[script_display, num_clips_state] + 
                [comp for acc in clip_accordions for comp in [acc[0], acc[1], acc[2], acc[3]]]
    ).then(
        fn=lambda x: gr.update(visible=True),
        inputs=[script_display],
        outputs=[script_display]
    )

    generate_video_button.click(
        fn=generate_video_full,
        inputs=[resolution, render_speed, video_clip_percent, zoom_pan_effect, 
                bgm_upload, bgm_volume, subtitles_enabled, num_clips_state] + 
               [comp for acc in clip_accordions for comp in acc[1:]],
        outputs=[output_video, download_button]
    )

demo.launch()