aheedsajid commited on
Commit
12e6406
1 Parent(s): e63b3e0

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +305 -0
  2. requirements (1).txt +5 -0
app.py ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+ from PIL import Image
6
+ import re
7
+ import asyncio
8
+ import edge_tts
9
+ import shutil
10
+ from gradio_client import Client
11
+ import subprocess
12
+ import uuid
13
+ import logging
14
+
15
+ # Set up logging
16
+ logging.basicConfig(level=logging.DEBUG)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Ensure necessary directories exist
20
+ os.makedirs("images", exist_ok=True)
21
+ os.makedirs("temp", exist_ok=True)
22
+ os.makedirs("output", exist_ok=True)
23
+
24
+ def generate_conversation(message):
25
+ client = Client("Groq/demo-groq-tool-use")
26
+ result = client.predict(
27
+ message=message,
28
+ system_message="""always give response in this json format:
29
+
30
+ {
31
+ "script": "short script here"
32
+ }
33
+
34
+ """,
35
+ api_name="/chat"
36
+ )
37
+ return result
38
+
39
+ def extract_json_content(response):
40
+ try:
41
+ match = re.search(r"'content': '(.+?)'(?=, 'metadata')", response, re.DOTALL)
42
+ if match:
43
+ json_str = match.group(1)
44
+ json_str = json_str.replace("\\'", "'")
45
+ content_json = json.loads(json_str)
46
+ return content_json
47
+ except Exception as e:
48
+ logger.error(f"Error extracting JSON content: {str(e)}")
49
+ return None
50
+
51
+ async def generate_voiceover(text, output_filename):
52
+ try:
53
+ tts = edge_tts.Communicate(text, voice="en-US-AvaNeural")
54
+ await tts.save(output_filename)
55
+ except Exception as e:
56
+ logger.error(f"Error generating: {str(e)}")
57
+ raise
58
+
59
+ def get_duration(file_path):
60
+ try:
61
+ cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', file_path]
62
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
63
+ data = json.loads(result.stdout)
64
+ return float(data['format']['duration'])
65
+ except Exception as e:
66
+ logger.error(f"Error getting duration: {str(e)}")
67
+ raise
68
+
69
+ def combine_audio_video(video_file, audio_file, output_file):
70
+ try:
71
+ video_duration = get_duration(video_file)
72
+ audio_duration = get_duration(audio_file)
73
+
74
+ if video_duration > audio_duration:
75
+ trim_cmd = [
76
+ 'ffmpeg', '-y', '-i', video_file, '-t', str(audio_duration),
77
+ '-c:v', 'libx264', '-c:a', 'aac', f'{output_file}_trimmed.mp4'
78
+ ]
79
+ subprocess.run(trim_cmd, check=True)
80
+ video_file = f'{output_file}_trimmed.mp4'
81
+ elif video_duration < audio_duration:
82
+ loop_count = int(audio_duration / video_duration) + 1
83
+ loop_cmd = [
84
+ 'ffmpeg', '-y', '-stream_loop', str(loop_count), '-i', video_file,
85
+ '-c', 'copy', f'{output_file}_looped.mp4'
86
+ ]
87
+ subprocess.run(loop_cmd, check=True)
88
+ video_file = f'{output_file}_looped.mp4'
89
+
90
+ combine_cmd = [
91
+ 'ffmpeg', '-y', '-i', video_file, '-i', audio_file,
92
+ '-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0',
93
+ '-shortest', output_file
94
+ ]
95
+ subprocess.run(combine_cmd, check=True)
96
+ except Exception as e:
97
+ logger.error(f"Error combining audio and video: {str(e)}")
98
+ raise
99
+
100
+ def add_background_to_image(image_path, output_path, video_width, video_height, background_color=(240, 240, 240)):
101
+ try:
102
+ with Image.open(image_path) as img:
103
+ # Create a new image with the background color
104
+ bg = Image.new('RGB', (video_width, video_height), color=background_color)
105
+
106
+ # Calculate the size for the original image (80% of the video width)
107
+ new_width = int(video_width * 0.8)
108
+ ratio = new_width / img.width
109
+ new_height = int(img.height * ratio)
110
+
111
+ # Resize the original image
112
+ img_resized = img.resize((new_width, new_height), Image.LANCZOS)
113
+
114
+ # Calculate position to paste the image (center)
115
+ position = ((video_width - new_width) // 2, (video_height - new_height) // 2)
116
+
117
+ # Paste the original image onto the background
118
+ bg.paste(img_resized, position, img_resized if img_resized.mode == 'RGBA' else None)
119
+
120
+ # Save the result
121
+ bg.save(output_path)
122
+ except Exception as e:
123
+ logger.error(f"Error adding background to image: {str(e)}")
124
+ raise
125
+
126
+ def create_ad_video(video_path, images_folder, output_path, num_images=5, image_duration=2, image_interval=3):
127
+ try:
128
+ # Get video dimensions
129
+ dimension_cmd = ['ffprobe', '-v', 'error', '-select_streams', 'v:0',
130
+ '-show_entries', 'stream=width,height', '-of', 'csv=s=x:p=0', video_path]
131
+ result = subprocess.run(dimension_cmd, capture_output=True, text=True, check=True)
132
+ video_width, video_height = map(int, result.stdout.strip().split('x'))
133
+
134
+ # Get video duration
135
+ duration_cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration',
136
+ '-of', 'default=noprint_wrappers=1:nokey=1', video_path]
137
+ result = subprocess.run(duration_cmd, capture_output=True, text=True, check=True)
138
+ video_duration = float(result.stdout.strip())
139
+
140
+ # Prepare filter complex for overlaying images
141
+ filter_complex = []
142
+ inputs = ['-i', video_path]
143
+ image_files = sorted([f for f in os.listdir(images_folder) if f.startswith('processed_image_')])[:num_images]
144
+
145
+ logger.debug(f"Found image files: {image_files}")
146
+
147
+ total_image_time = len(image_files) * (image_duration + image_interval)
148
+ if total_image_time > video_duration:
149
+ num_images = int(video_duration / (image_duration + image_interval))
150
+ image_files = image_files[:num_images]
151
+
152
+ # Add input files and create overlays
153
+ for i, image_file in enumerate(image_files):
154
+ image_path = os.path.join(images_folder, image_file)
155
+ inputs.extend(['-i', image_path])
156
+
157
+ start_time = 3 + i * (image_duration + image_interval)
158
+
159
+ if i == 0:
160
+ filter_complex.append(f"[0:v][{i+1}:v]overlay=enable='between(t,{start_time},{start_time + image_duration})'[v{i}];")
161
+ else:
162
+ filter_complex.append(f"[v{i-1}][{i+1}:v]overlay=enable='between(t,{start_time},{start_time + image_duration})'[v{i}];")
163
+
164
+ # Construct final filter complex string
165
+ filter_complex_str = ''.join(filter_complex)
166
+
167
+ if not filter_complex_str:
168
+ # If no images to overlay, just copy the video
169
+ cmd = ['ffmpeg', '-y', '-i', video_path, '-c', 'copy', output_path]
170
+ else:
171
+ # Remove the last semicolon
172
+ filter_complex_str = filter_complex_str.rstrip(';')
173
+
174
+ # Construct FFmpeg command with all inputs and filter complex
175
+ cmd = ['ffmpeg', '-y'] + inputs + [
176
+ '-filter_complex', filter_complex_str,
177
+ '-map', f'[v{len(image_files)-1}]' if image_files else '[0:v]',
178
+ '-map', '0:a',
179
+ '-c:a', 'copy',
180
+ output_path
181
+ ]
182
+
183
+ logger.debug(f"FFmpeg command: {' '.join(cmd)}")
184
+
185
+ # Execute FFmpeg command
186
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
187
+ logger.info("FFmpeg command executed successfully")
188
+ return True
189
+
190
+ except subprocess.CalledProcessError as e:
191
+ logger.error(f"FFmpeg error: {e.stderr}")
192
+ raise
193
+ except Exception as e:
194
+ logger.error(f"Error in create_ad_video: {str(e)}")
195
+ raise
196
+
197
+ def create_video_ad(title, description, images, base_video, progress=gr.Progress()):
198
+ session_id = str(uuid.uuid4())
199
+ session_folder = os.path.join("temp", session_id)
200
+
201
+ try:
202
+ os.makedirs(session_folder, exist_ok=True)
203
+
204
+ progress(0, desc="Saving data and processing images...")
205
+ # Step 1: Save data and process images
206
+ data = {
207
+ "title": title,
208
+ "description": description,
209
+ }
210
+
211
+ with open(os.path.join(session_folder, "scraped_data.json"), "w") as f:
212
+ json.dump(data, f, indent=4)
213
+
214
+ # Process video file
215
+ temp_video_path = os.path.join(session_folder, "temp_video.mp4")
216
+ if isinstance(base_video, gr.File):
217
+ shutil.copy(base_video.name, temp_video_path)
218
+ elif isinstance(base_video, str):
219
+ shutil.copy(base_video, temp_video_path)
220
+ else:
221
+ raise Exception("Unexpected type for base_video")
222
+
223
+ # Get video dimensions
224
+ dimension_cmd = ['ffprobe', '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'csv=s=x:p=0', temp_video_path]
225
+ video_width, video_height = map(int, subprocess.check_output(dimension_cmd).decode().strip().split('x'))
226
+
227
+ saved_image_paths = []
228
+ for i, image_path in enumerate(images[:5]): # Limit to 5 images
229
+ original_image_path = os.path.join(session_folder, f"original_image_{i+1}.png")
230
+ Image.open(image_path).save(original_image_path)
231
+
232
+ processed_image_path = os.path.join(session_folder, f"processed_image_{i+1}.png")
233
+ add_background_to_image(original_image_path, processed_image_path, video_width, video_height)
234
+ saved_image_paths.append(processed_image_path)
235
+
236
+ progress(0.2, desc="Generating...")
237
+ # Step 2: Generate script
238
+ user_message = f"Create a very short simple text ad script for a product with title: '{title}' and description: '{description}'"
239
+ response_data = generate_conversation(user_message)
240
+ simplified_data = extract_json_content(str(response_data))
241
+
242
+ if not simplified_data:
243
+ raise Exception("Failed to generate ad script")
244
+
245
+ if isinstance(simplified_data, dict):
246
+ script_content = simplified_data.get("script", "")
247
+ elif isinstance(simplified_data, str):
248
+ script_content = simplified_data
249
+ else:
250
+ raise Exception("Unexpected data type for script content")
251
+
252
+ if not script_content:
253
+ raise Exception("Generated script is empty")
254
+
255
+ with open(os.path.join(session_folder, "script.json"), "w") as outfile:
256
+ json.dump({"script": script_content}, outfile, indent=2)
257
+
258
+ progress(0.4, desc="Generating...")
259
+ # Step 3: Generate voiceover
260
+ voiceover_path = os.path.join(session_folder, "voiceover.mp3")
261
+ asyncio.run(generate_voiceover(script_content, voiceover_path))
262
+
263
+ progress(0.6, desc="Processing...")
264
+ # Step 4: Handle video file (already done when getting dimensions)
265
+
266
+ progress(0.8, desc="Creating final...")
267
+ # Step 5: Combine audio and video, add images
268
+ combined_output = os.path.join(session_folder, "combined_video.mp4")
269
+ final_output = os.path.join("output", f"{title.replace(' ', '_')}_ad_{session_id}.mp4")
270
+
271
+ # Combine base video with audio
272
+ combine_audio_video(temp_video_path, voiceover_path, combined_output)
273
+
274
+ # Overlay images on the combined video
275
+ create_ad_video(combined_output, session_folder, final_output, num_images=len(saved_image_paths))
276
+
277
+ progress(1.0, desc="Cleaning up...")
278
+ # Clean up temporary files
279
+ shutil.rmtree(session_folder)
280
+
281
+ return final_output
282
+
283
+ except Exception as e:
284
+ logger.error(f"Error in create_video_ad: {str(e)}")
285
+ if os.path.exists(session_folder):
286
+ shutil.rmtree(session_folder)
287
+ return f"Error occurred: {str(e)}"
288
+
289
+ # Define the Gradio interface
290
+ iface = gr.Interface(
291
+ fn=create_video_ad,
292
+ inputs=[
293
+ gr.Textbox(label="Title"),
294
+ gr.Textbox(label="Description", lines=3),
295
+ gr.File(label="Upload (max 5)", file_count="multiple"),
296
+ gr.File(label="Upload")
297
+ ],
298
+ outputs=gr.Video(label="Generated JSON"),
299
+ title="Json",
300
+ description="Enter json data"
301
+ )
302
+
303
+ # Launch the interface
304
+ if __name__ == "__main__":
305
+ iface.launch()
requirements (1).txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ Pillow
3
+ edge-tts
4
+ gradio_client
5
+ moviepy