Video_Maker / app.py
Pandrive786's picture
Update app.py
76fc9ce verified
import gradio as gr
import os
import numpy as np
# MoviePy 2.x Imports
from moviepy import ImageClip, AudioFileClip, CompositeVideoClip, TextClip, ColorClip, concatenate_videoclips, VideoFileClip
from gtts import gTTS
import tempfile
from PIL import Image
# --- Helper: Resolution & Bitrate ---
def get_specs(ratio, quality):
res_map = {
"144p": (256, 144, "300k"),
"240p": (426, 240, "600k"),
"360p": (640, 360, "1000k"),
"720p": (1280, 720, "2500k"),
"1080p": (1920, 1080, "5000k")
}
w, h, b = res_map.get(quality, res_map["360p"])
if ratio == "9:16 (Shorts)": return h, w, b
elif ratio == "1:1 (Square)": return h, h, b
else: return w, h, b # 16:9
# --- TTS Function with Language ---
def text_to_audio(text, lang='en'):
if not text: return None
try:
t = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
gTTS(text=text, lang=lang).save(t.name)
return t.name
except Exception as e:
print(f"TTS Error: {e}")
return None
# --- Function 1: Create Ad ---
def create_controlled_ad(bg_img, char_img, prod_img, audio_file, text_input, lang_select, ratio, quality, overlay_text,
c_x, c_y, c_zoom, p_x, p_y, p_zoom):
# 1. Audio Handling
audio_path = audio_file
if not audio_path and text_input:
audio_path = text_to_audio(text_input, lang=lang_select)
if not audio_path:
return None, "Please provide Audio File or Text to Speak."
try:
W, H, bitrate = get_specs(ratio, quality)
audio_clip = AudioFileClip(audio_path)
duration = audio_clip.duration
clips = []
# 2. Background Layer
if bg_img is not None:
if isinstance(bg_img, np.ndarray):
bg_pil = Image.fromarray(bg_img)
else:
bg_pil = Image.open(bg_img)
bg_resized = bg_pil.resize((W, H), Image.LANCZOS)
bg_clip = ImageClip(np.array(bg_resized)).with_duration(duration)
else:
bg_clip = ColorClip(size=(W, H), color=(20, 20, 20)).with_duration(duration)
clips.append(bg_clip)
# Helper to place images
def place_image(img_input, pos_x, pos_y, zoom_factor, default_size_pct):
if img_input is None: return
if isinstance(img_input, np.ndarray):
pil_img = Image.fromarray(img_input)
else:
pil_img = Image.open(img_input)
base_h = int(H * default_size_pct)
base_w = int(base_h * (pil_img.width / pil_img.height))
final_h = int(base_h * zoom_factor)
final_w = int(base_w * zoom_factor)
if final_w <= 0: final_w = 1
if final_h <= 0: final_h = 1
resized_pil = pil_img.resize((final_w, final_h), Image.LANCZOS)
clip = ImageClip(np.array(resized_pil)).with_duration(duration)
center_x = (W - final_w) // 2
center_y = (H - final_h) // 2
offset_x = int((pos_x / 100) * W)
offset_y = int((pos_y / 100) * H)
final_pos = (center_x + offset_x, center_y + offset_y)
clips.append(clip.with_position(final_pos))
# 3. Add Character & Product
place_image(char_img, c_x, c_y, c_zoom, 0.50)
place_image(prod_img, p_x, p_y, p_zoom, 0.30)
# 4. Add Text Overlay (FIXED for MoviePy 2.x)
if overlay_text and len(str(overlay_text).strip()) > 0:
fontsize = max(12, int(W * 0.05))
try:
# Try simple label first
txt_clip = TextClip(
str(overlay_text),
font_size=fontsize,
color='white',
stroke_color='black',
stroke_width=2,
font='Arial-Bold'
)
# Wrap text if too wide
if txt_clip.w > W - 40:
txt_clip = TextClip(
str(overlay_text),
font_size=fontsize,
color='white',
stroke_color='black',
stroke_width=2,
size=(W-40, None),
method='caption'
)
txt_y = H - txt_clip.h - 20
clips.append(txt_clip.with_position(('center', txt_y)).with_duration(duration))
except Exception as text_err:
print(f"Text Clip Error: {text_err}")
pass
# 5. Render Video
final_video = CompositeVideoClip(clips, size=(W, H)).with_audio(audio_clip)
output_file = "ad_part.mp4"
final_video.write_videofile(
output_file,
fps=24,
codec='libx264',
audio_codec='aac',
preset='ultrafast',
threads=4,
logger=None
)
final_video.close()
audio_clip.close()
if not audio_file and audio_path and os.path.exists(audio_path):
os.remove(audio_path)
return output_file, f"Part Created! Use Merge Tab to join more."
except Exception as e:
import traceback
return None, f"Error: {str(e)}\n{traceback.format_exc()}"
# --- Function 2: Merge Videos ---
def merge_videos(video_list):
if not video_list or len(video_list) < 2:
return None, "Please upload at least 2 videos to merge."
clips = []
try:
for video_file in video_list:
clips.append(VideoFileClip(str(video_file)))
final_clip = concatenate_videoclips(clips, method="compose")
output_file = "merged_final.mp4"
final_clip.write_videofile(
output_file,
fps=24,
codec='libx264',
audio_codec='aac',
preset='ultrafast',
threads=4,
logger=None
)
for c in clips: c.close()
return output_file, "Videos Merged Successfully!"
except Exception as e:
for c in clips:
try: c.close()
except: pass
return None, f"Merge Error: {str(e)}"
# --- UI Design (Gradio 6 Compatible) ---
with gr.Blocks() as demo:
gr.Markdown("# 🎛️ Pro Ad Maker & Merger (Final)")
with gr.Tabs():
# TAB 1: CREATE AD
with gr.TabItem("1. Create Ad Part"):
with gr.Row():
with gr.Column(scale=1):
bg_in = gr.Image(label="1. Background", type="numpy")
char_in = gr.Image(label="2. Character", type="numpy")
prod_in = gr.Image(label="3. Product", type="numpy")
with gr.Accordion("Settings", open=True):
ratio_in = gr.Radio(["16:9", "9:16", "1:1"], value="9:16", label="Ratio")
qual_in = gr.Radio(["144p", "360p", "720p", "1080p"], value="360p", label="Quality")
lang_in = gr.Dropdown(choices=[("English", "en"), ("Hindi", "hi")], value="hi", label="Language")
txt_in = gr.Textbox(
lines=2,
placeholder="Script... (Copy-Paste allowed)",
label="Text to Speech"
)
aud_in = gr.Audio(label="OR Upload Audio")
gen_btn = gr.Button("🎥 Generate Part", variant="primary")
with gr.Column(scale=1):
gr.Markdown("### 🕹️ Adjust Position")
with gr.Tab("Character"):
c_zoom = gr.Slider(0.1, 2.0, value=1.0, label="Zoom")
c_x = gr.Slider(-50, 50, value=-20, label="Horiz (Left/Right)")
c_y = gr.Slider(-50, 50, value=10, label="Vert (Up/Down)")
with gr.Tab("Product"):
p_zoom = gr.Slider(0.1, 2.0, value=1.0, label="Zoom")
p_x = gr.Slider(-50, 50, value=20, label="Horiz (Left/Right)")
p_y = gr.Slider(-50, 50, value=0, label="Vert (Up/Down)")
vid_out_1 = gr.Video(label="Generated Part")
status_1 = gr.Textbox(label="Status")
gen_btn.click(
fn=create_controlled_ad,
inputs=[bg_in, char_in, prod_in, aud_in, txt_in, lang_in, ratio_in, qual_in, txt_in, c_x, c_y, c_zoom, p_x, p_y, p_zoom],
outputs=[vid_out_1, status_1]
)
# TAB 2: MERGE VIDEOS
with gr.TabItem("2. Merge Parts"):
gr.Markdown("Upload multiple generated parts to join them into one long video.")
with gr.Row():
with gr.Column():
files_in = gr.File(file_count="multiple", label="Select Video Parts")
merge_btn = gr.Button("🔗 Merge Videos", variant="primary")
with gr.Column():
vid_out_2 = gr.Video(label="Final Long Video")
status_2 = gr.Textbox(label="Status")
merge_btn.click(fn=merge_videos, inputs=[files_in], outputs=[vid_out_2, status_2])
if __name__ == "__main__":
demo.launch(
theme=gr.themes.Soft(),
server_name="0.0.0.0",
server_port=7860,
share=True
)