Pandrive786 commited on
Commit
910d7ff
·
verified ·
1 Parent(s): e3c6953

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -0
app.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import numpy as np
4
+ from moviepy.editor import ImageClip, AudioFileClip, CompositeVideoClip, TextClip, ColorClip
5
+ from gtts import gTTS
6
+ import tempfile
7
+ from PIL import Image
8
+
9
+ # --- Helper: Resolution & Bitrate ---
10
+ def get_specs(ratio, quality):
11
+ res_map = {
12
+ "144p": (256, 144, "300k"),
13
+ "240p": (426, 240, "600k"),
14
+ "360p": (640, 360, "1000k"),
15
+ "720p": (1280, 720, "2500k"),
16
+ "1080p": (1920, 1080, "5000k")
17
+ }
18
+ w, h, b = res_map.get(quality, res_map["360p"])
19
+
20
+ if ratio == "9:16 (Shorts)": return h, w, b
21
+ elif ratio == "1:1 (Square)": return h, h, b
22
+ else: return w, h, b # 16:9
23
+
24
+ # --- TTS Function with Language ---
25
+ def text_to_audio(text, lang='en'):
26
+ if not text: return None
27
+ try:
28
+ t = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
29
+ gTTS(text=text, lang=lang).save(t.name)
30
+ return t.name
31
+ except Exception as e:
32
+ print(f"TTS Error: {e}")
33
+ return None
34
+
35
+ # --- Main Video Generation ---
36
+ def create_controlled_ad(bg_img, char_img, prod_img, audio_file, text_input, lang_select, ratio, quality, overlay_text,
37
+ c_x, c_y, c_zoom, p_x, p_y, p_zoom):
38
+
39
+ # 1. Audio Handling
40
+ audio_path = audio_file
41
+ if not audio_path and text_input:
42
+ audio_path = text_to_audio(text_input, lang=lang_select)
43
+
44
+ if not audio_path:
45
+ return None, "Please provide Audio File or Text to Speak."
46
+
47
+ try:
48
+ W, H, bitrate = get_specs(ratio, quality)
49
+ audio_clip = AudioFileClip(audio_path)
50
+ duration = audio_clip.duration
51
+
52
+ clips = []
53
+
54
+ # 2. Background Layer (Fixed for Gradio Array/Input handling)
55
+ if bg_img is not None:
56
+ if isinstance(bg_img, np.ndarray):
57
+ bg_pil = Image.fromarray(bg_img)
58
+ else:
59
+ bg_pil = Image.open(bg_img)
60
+
61
+ bg_clip = ImageClip(np.array(bg_pil.resize((W, H)))).set_duration(duration)
62
+ else:
63
+ bg_clip = ColorClip(size=(W, H), color=(20, 20, 20)).set_duration(duration)
64
+
65
+ clips.append(bg_clip)
66
+
67
+ # Helper to place images with Sliders
68
+ def place_image(img_input, pos_x, pos_y, zoom_factor, default_size_pct):
69
+ if img_input is None: return
70
+
71
+ if isinstance(img_input, np.ndarray):
72
+ pil_img = Image.fromarray(img_input)
73
+ else:
74
+ pil_img = Image.open(img_input)
75
+
76
+ base_h = int(H * default_size_pct)
77
+ base_w = int(base_h * (pil_img.width / pil_img.height))
78
+
79
+ final_h = int(base_h * zoom_factor)
80
+ final_w = int(base_w * zoom_factor)
81
+
82
+ if final_w <= 0: final_w = 1
83
+ if final_h <= 0: final_h = 1
84
+
85
+ clip = ImageClip(np.array(pil_img)).resize((final_w, final_h)).set_duration(duration)
86
+
87
+ center_x = (W - final_w) // 2
88
+ center_y = (H - final_h) // 2
89
+
90
+ offset_x = int((pos_x / 100) * W)
91
+ offset_y = int((pos_y / 100) * H)
92
+
93
+ final_pos = (center_x + offset_x, center_y + offset_y)
94
+ clips.append(clip.set_position(final_pos))
95
+
96
+ # 3. Add Character
97
+ place_image(char_img, c_x, c_y, c_zoom, 0.50)
98
+
99
+ # 4. Add Product
100
+ place_image(prod_img, p_x, p_y, p_zoom, 0.30)
101
+
102
+ # 5. Add Text Overlay
103
+ if overlay_text and len(str(overlay_text).strip()) > 0:
104
+ fontsize = max(12, int(W * 0.05))
105
+ txt_clip = TextClip(str(overlay_text), fontsize=fontsize, color='white', stroke_color='black', stroke_width=2, method='caption', size=(W-20, None))
106
+ txt_y = H - txt_clip.h - 20
107
+ clips.append(txt_clip.set_position(('center', txt_y)).set_duration(duration))
108
+
109
+ # 6. Render Video
110
+ final_video = CompositeVideoClip(clips, size=(W, H)).set_audio(audio_clip)
111
+ output_file = "controlled_ad.mp4"
112
+
113
+ final_video.write_videofile(
114
+ output_file,
115
+ fps=24,
116
+ codec='libx264',
117
+ audio_codec='aac',
118
+ preset='ultrafast',
119
+ threads=4,
120
+ bitrate=bitrate
121
+ )
122
+
123
+ final_video.close()
124
+ audio_clip.close()
125
+ if not audio_file and audio_path and os.path.exists(audio_path):
126
+ os.remove(audio_path)
127
+
128
+ return output_file, f"Video Generated in {lang_select.upper()}!"
129
+
130
+ except Exception as e:
131
+ import traceback
132
+ return None, f"Error: {str(e)}\n{traceback.format_exc()}"
133
+
134
+ # --- UI Design ---
135
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
136
+ gr.Markdown("# 🎛️ Pro Ad Maker: Multi-Language & Control")
137
+
138
+ with gr.Row():
139
+ with gr.Column(scale=1):
140
+ bg_in = gr.Image(label="1. Background Image", type="numpy")
141
+ char_in = gr.Image(label="2. Character Image", type="numpy")
142
+ prod_in = gr.Image(label="3. Product Image", type="numpy")
143
+
144
+ with gr.Accordion("Video & Voice Settings", open=True):
145
+ ratio_in = gr.Radio(["16:9 (YouTube)", "9:16 (Shorts)", "1:1 (Post)"], value="9:16 (Shorts)", label="Ratio")
146
+ qual_in = gr.Radio(["144p", "240p", "360p", "720p", "1080p"], value="360p", label="Quality")
147
+
148
+ lang_in = gr.Dropdown(
149
+ choices=[
150
+ ("English", "en"),
151
+ ("Hindi", "hi"),
152
+ ("Spanish", "es"),
153
+ ("French", "fr"),
154
+ ("German", "de"),
155
+ ("Japanese", "ja")
156
+ ],
157
+ value="hi",
158
+ label="Select Voice Language"
159
+ )
160
+
161
+ txt_in = gr.Textbox(lines=2, placeholder="Ad Script (e.g., 'Ye product best hai!')", label="Text to Speech")
162
+ aud_in = gr.Audio(label="OR Upload Audio File (Overrides Text)")
163
+
164
+ generate_btn = gr.Button("🎥 Generate Video", variant="primary")
165
+
166
+ with gr.Column(scale=1):
167
+ gr.Markdown("### 🕹️ Alignment Controls")
168
+
169
+ with gr.Tab("Character Adjust"):
170
+ c_zoom = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Character Zoom (Size)")
171
+ c_x = gr.Slider(-50, 50, value=-20, step=1, label="Character Horizontal (Left/Right)")
172
+ c_y = gr.Slider(-50, 50, value=10, step=1, label="Character Vertical (Up/Down)")
173
+
174
+ with gr.Tab("Product Adjust"):
175
+ p_zoom = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Product Zoom (Size)")
176
+ p_x = gr.Slider(-50, 50, value=20, step=1, label="Product Horizontal (Left/Right)")
177
+ p_y = gr.Slider(-50, 50, value=0, step=1, label="Product Vertical (Up/Down)")
178
+
179
+ vid_out = gr.Video(label="Final Video")
180
+ status_out = gr.Textbox(label="Status")
181
+
182
+ generate_btn.click(
183
+ fn=create_controlled_ad,
184
+ inputs=[
185
+ bg_in, char_in, prod_in, aud_in, txt_in, lang_in, ratio_in, qual_in, txt_in,
186
+ c_x, c_y, c_zoom,
187
+ p_x, p_y, p_zoom
188
+ ],
189
+ outputs=[vid_out, status_out]
190
+ )
191
+
192
+ if __name__ == "__main__":
193
+ demo.launch()