ladybug11 commited on
Commit
8506538
Β·
1 Parent(s): 5702453

Add MoviePy video overlay - complete pipeline

Browse files
Files changed (1) hide show
  1. app.py +183 -28
app.py CHANGED
@@ -2,9 +2,12 @@ import gradio as gr
2
  import os
3
  import requests
4
  import random
 
5
  from openai import OpenAI
6
  from smolagents import CodeAgent, MCPClient, tool
7
  from huggingface_hub import InferenceClient
 
 
8
 
9
  # Initialize clients
10
  openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
@@ -184,6 +187,107 @@ def search_pexels_video_tool(style: str, niche: str) -> dict:
184
  "error": str(e)
185
  }
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  # Initialize the MCP-powered agent
188
  def initialize_agent():
189
  """Initialize the CodeAgent with MCP capabilities"""
@@ -193,10 +297,10 @@ def initialize_agent():
193
 
194
  # Create agent with custom tools
195
  agent = CodeAgent(
196
- tools=[generate_quote_tool, search_pexels_video_tool],
197
  model=model,
198
- additional_authorized_imports=["requests", "openai", "random"],
199
- max_steps=5
200
  )
201
 
202
  # Add MCP client if available
@@ -229,14 +333,14 @@ def mcp_agent_pipeline(niche, style):
229
  status_log.append("πŸ“‹ **TASK RECEIVED:**")
230
  status_log.append(f" β†’ Generate {niche} quote with {style} aesthetic")
231
  status_log.append(f" β†’ Find matching video")
232
- status_log.append(f" β†’ Prepare for video creation\n")
233
 
234
  # STEP 2: Agent executes quote generation
235
  status_log.append("🧠 **MCP TOOL: generate_quote_tool**")
236
  quote = generate_quote_tool(niche, style)
237
 
238
  if "Error" in quote:
239
- return "\n".join(status_log) + f"\n❌ Failed: {quote}", None
240
 
241
  status_log.append(f" βœ… Generated: \"{quote}\"\n")
242
 
@@ -246,12 +350,40 @@ def mcp_agent_pipeline(niche, style):
246
 
247
  if not video_result["success"]:
248
  error_msg = video_result.get("error", "Unknown error")
249
- return "\n".join(status_log) + f"\n❌ Video search failed: {error_msg}", None
250
 
251
  status_log.append(f" βœ… Found video: {video_result['search_query']}")
252
  status_log.append(f" πŸ“₯ Video URL: {video_result['video_url']}\n")
253
 
254
- # STEP 4: MCP Server integration status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  status_log.append("πŸ”— **MCP SERVER STATUS:**")
256
  if mcp_enabled:
257
  status_log.append(" βœ… Connected to: abidlabs-mcp-tools.hf.space")
@@ -260,21 +392,17 @@ def mcp_agent_pipeline(niche, style):
260
  status_log.append(" ⚠️ MCP server connection pending")
261
  status_log.append("")
262
 
263
- # STEP 5: Next steps
264
- status_log.append("🚧 **NEXT STEPS (Coming Soon):**")
265
- status_log.append(" β†’ Text overlay with MoviePy")
266
- status_log.append(" β†’ Voice narration with ElevenLabs")
267
- status_log.append(" β†’ Final video export\n")
268
-
269
- status_log.append("🎬 **PIPELINE COMPLETE!**")
270
- status_log.append(f"πŸ“± View on Pexels: {video_result['pexels_url']}")
271
 
272
  final_status = "\n".join(status_log)
273
- return final_status, video_result["video_url"]
274
 
275
  except Exception as e:
276
  status_log.append(f"\n❌ Pipeline error: {str(e)}")
277
- return "\n".join(status_log), None
278
 
279
  def fallback_pipeline(niche, style):
280
  """Fallback pipeline if MCP agent fails"""
@@ -286,7 +414,7 @@ def fallback_pipeline(niche, style):
286
  quote = generate_quote_tool(niche, style)
287
 
288
  if "Error" in quote:
289
- return "\n".join(status_log) + f"\n❌ {quote}", None
290
 
291
  status_log.append(f" βœ… Quote: \"{quote}\"\n")
292
 
@@ -295,12 +423,33 @@ def fallback_pipeline(niche, style):
295
  video_result = search_pexels_video_tool(style, niche)
296
 
297
  if not video_result["success"]:
298
- return "\n".join(status_log) + f"\n❌ {video_result.get('error', 'Failed')}", None
299
 
300
  status_log.append(f" βœ… Found: {video_result['search_query']}\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  status_log.append("🎬 **COMPLETE!**")
302
 
303
- return "\n".join(status_log), video_result["video_url"]
304
 
305
  # Gradio Interface
306
  with gr.Blocks(title="AIQuoteClipGenerator - MCP Edition", theme=gr.themes.Soft()) as demo:
@@ -310,7 +459,7 @@ with gr.Blocks(title="AIQuoteClipGenerator - MCP Edition", theme=gr.themes.Soft(
310
 
311
  **MCP Integration Features:**
312
  - πŸ”— **MCP Server:** Connected to smolagents framework
313
- - πŸ› οΈ **Custom Tools:** Quote generation + Video search
314
  - πŸ€– **Agent Reasoning:** Autonomous task execution
315
  - ⚑ **Tool Orchestration:** Intelligent pipeline management
316
  """)
@@ -351,29 +500,35 @@ with gr.Blocks(title="AIQuoteClipGenerator - MCP Edition", theme=gr.themes.Soft(
351
  output = gr.Textbox(label="Agent Status", lines=20, show_label=False)
352
 
353
  with gr.Row():
354
- video_output = gr.Video(label="πŸŽ₯ Video Preview")
 
 
 
 
 
 
355
 
356
  gr.Markdown("""
357
  ---
358
  ### ✨ MCP Implementation
359
  - βœ… **smolagents Framework** - Proper MCP integration
360
- - βœ… **Custom MCP Tools** - Quote generation & video search
361
  - βœ… **CodeAgent** - Autonomous reasoning and execution
362
  - βœ… **MCP Client** - Connected to external MCP servers
363
- - 🚧 **MoviePy Processing** - Text overlay (next)
364
- - 🚧 **ElevenLabs Integration** - Voice narration (next)
365
 
366
  ### πŸ† Hackathon: MCP 1st Birthday
367
  **Track:** Track 2 - MCP in Action
368
  **Category:** Productivity Tools
369
- **Built with:** Gradio + smolagents + OpenAI + Pexels + MCP
370
  """)
371
 
372
  generate_btn.click(
373
  mcp_agent_pipeline,
374
  inputs=[niche, style],
375
- outputs=[output, video_output]
376
  )
377
 
378
  if __name__ == "__main__":
379
- demo.launch()
 
2
  import os
3
  import requests
4
  import random
5
+ import tempfile
6
  from openai import OpenAI
7
  from smolagents import CodeAgent, MCPClient, tool
8
  from huggingface_hub import InferenceClient
9
+ from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip
10
+ from moviepy.config import change_settings
11
 
12
  # Initialize clients
13
  openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
 
187
  "error": str(e)
188
  }
189
 
190
+ @tool
191
+ def create_quote_video_tool(video_url: str, quote_text: str, output_path: str) -> dict:
192
+ """
193
+ Create a final quote video by overlaying text on the background video.
194
+
195
+ Args:
196
+ video_url: URL of the background video from Pexels
197
+ quote_text: The quote text to overlay
198
+ output_path: Path where to save the final video
199
+
200
+ Returns:
201
+ Dictionary with success status and output path
202
+ """
203
+
204
+ try:
205
+ # Step 1: Download the video
206
+ response = requests.get(video_url, stream=True, timeout=30)
207
+ response.raise_for_status()
208
+
209
+ # Create temporary file for downloaded video
210
+ temp_video = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
211
+
212
+ with open(temp_video.name, 'wb') as f:
213
+ for chunk in response.iter_content(chunk_size=8192):
214
+ f.write(chunk)
215
+
216
+ # Step 2: Load video with MoviePy
217
+ video = VideoFileClip(temp_video.name)
218
+
219
+ # Get video dimensions
220
+ w, h = video.size
221
+
222
+ # Step 3: Create text overlay
223
+ # Calculate font size based on video height (responsive)
224
+ font_size = int(h * 0.06) # 6% of video height
225
+
226
+ # Split quote into multiple lines for better readability
227
+ words = quote_text.split()
228
+ lines = []
229
+ current_line = []
230
+ max_words_per_line = 4
231
+
232
+ for word in words:
233
+ current_line.append(word)
234
+ if len(current_line) >= max_words_per_line:
235
+ lines.append(' '.join(current_line))
236
+ current_line = []
237
+
238
+ if current_line:
239
+ lines.append(' '.join(current_line))
240
+
241
+ # Join lines with newline
242
+ formatted_text = '\n'.join(lines)
243
+
244
+ # Create text clip with styling
245
+ txt_clip = TextClip(
246
+ formatted_text,
247
+ fontsize=font_size,
248
+ color='white',
249
+ font='Arial-Bold',
250
+ stroke_color='black',
251
+ stroke_width=2,
252
+ method='caption',
253
+ size=(int(w * 0.9), None), # 90% of video width
254
+ align='center'
255
+ )
256
+
257
+ # Position text in center
258
+ txt_clip = txt_clip.set_position('center').set_duration(video.duration)
259
+
260
+ # Step 4: Composite video with text
261
+ final_video = CompositeVideoClip([video, txt_clip])
262
+
263
+ # Step 5: Export final video
264
+ final_video.write_videofile(
265
+ output_path,
266
+ codec='libx264',
267
+ audio_codec='aac',
268
+ temp_audiofile='temp-audio.m4a',
269
+ remove_temp=True,
270
+ fps=24
271
+ )
272
+
273
+ # Cleanup
274
+ video.close()
275
+ final_video.close()
276
+ os.unlink(temp_video.name)
277
+
278
+ return {
279
+ "success": True,
280
+ "output_path": output_path,
281
+ "message": "Video created successfully!"
282
+ }
283
+
284
+ except Exception as e:
285
+ return {
286
+ "success": False,
287
+ "output_path": None,
288
+ "message": f"Error creating video: {str(e)}"
289
+ }
290
+
291
  # Initialize the MCP-powered agent
292
  def initialize_agent():
293
  """Initialize the CodeAgent with MCP capabilities"""
 
297
 
298
  # Create agent with custom tools
299
  agent = CodeAgent(
300
+ tools=[generate_quote_tool, search_pexels_video_tool, create_quote_video_tool],
301
  model=model,
302
+ additional_authorized_imports=["requests", "openai", "random", "tempfile", "os"],
303
+ max_steps=10
304
  )
305
 
306
  # Add MCP client if available
 
333
  status_log.append("πŸ“‹ **TASK RECEIVED:**")
334
  status_log.append(f" β†’ Generate {niche} quote with {style} aesthetic")
335
  status_log.append(f" β†’ Find matching video")
336
+ status_log.append(f" β†’ Create final quote video\n")
337
 
338
  # STEP 2: Agent executes quote generation
339
  status_log.append("🧠 **MCP TOOL: generate_quote_tool**")
340
  quote = generate_quote_tool(niche, style)
341
 
342
  if "Error" in quote:
343
+ return "\n".join(status_log) + f"\n❌ Failed: {quote}", None, None
344
 
345
  status_log.append(f" βœ… Generated: \"{quote}\"\n")
346
 
 
350
 
351
  if not video_result["success"]:
352
  error_msg = video_result.get("error", "Unknown error")
353
+ return "\n".join(status_log) + f"\n❌ Video search failed: {error_msg}", None, None
354
 
355
  status_log.append(f" βœ… Found video: {video_result['search_query']}")
356
  status_log.append(f" πŸ“₯ Video URL: {video_result['video_url']}\n")
357
 
358
+ # STEP 4: Agent creates final video with text overlay
359
+ status_log.append("🎬 **MCP TOOL: create_quote_video_tool**")
360
+ status_log.append(" ⏳ Creating video with text overlay...")
361
+
362
+ # Create output directory if it doesn't exist
363
+ output_dir = "/tmp/quote_videos"
364
+ os.makedirs(output_dir, exist_ok=True)
365
+
366
+ # Generate unique filename
367
+ import time
368
+ output_filename = f"quote_video_{int(time.time())}.mp4"
369
+ output_path = os.path.join(output_dir, output_filename)
370
+
371
+ # Create the video
372
+ creation_result = create_quote_video_tool(
373
+ video_result["video_url"],
374
+ quote,
375
+ output_path
376
+ )
377
+
378
+ if not creation_result["success"]:
379
+ status_log.append(f" ❌ Video creation failed: {creation_result['message']}")
380
+ status_log.append("\nπŸ“Ί **PREVIEW MODE:**")
381
+ status_log.append(" Showing background video preview instead")
382
+ return "\n".join(status_log), video_result["video_url"], None
383
+
384
+ status_log.append(f" βœ… Video created successfully!\n")
385
+
386
+ # STEP 5: MCP Server integration status
387
  status_log.append("πŸ”— **MCP SERVER STATUS:**")
388
  if mcp_enabled:
389
  status_log.append(" βœ… Connected to: abidlabs-mcp-tools.hf.space")
 
392
  status_log.append(" ⚠️ MCP server connection pending")
393
  status_log.append("")
394
 
395
+ # STEP 6: Success!
396
+ status_log.append("✨ **PIPELINE COMPLETE!**")
397
+ status_log.append(f" πŸ“± Original video: {video_result['pexels_url']}")
398
+ status_log.append(f" 🎬 Final video ready for download!")
 
 
 
 
399
 
400
  final_status = "\n".join(status_log)
401
+ return final_status, video_result["video_url"], creation_result["output_path"]
402
 
403
  except Exception as e:
404
  status_log.append(f"\n❌ Pipeline error: {str(e)}")
405
+ return "\n".join(status_log), None, None
406
 
407
  def fallback_pipeline(niche, style):
408
  """Fallback pipeline if MCP agent fails"""
 
414
  quote = generate_quote_tool(niche, style)
415
 
416
  if "Error" in quote:
417
+ return "\n".join(status_log) + f"\n❌ {quote}", None, None
418
 
419
  status_log.append(f" βœ… Quote: \"{quote}\"\n")
420
 
 
423
  video_result = search_pexels_video_tool(style, niche)
424
 
425
  if not video_result["success"]:
426
+ return "\n".join(status_log) + f"\n❌ {video_result.get('error', 'Failed')}", None, None
427
 
428
  status_log.append(f" βœ… Found: {video_result['search_query']}\n")
429
+
430
+ # Create video
431
+ status_log.append("🎬 Creating final video...")
432
+ output_dir = "/tmp/quote_videos"
433
+ os.makedirs(output_dir, exist_ok=True)
434
+
435
+ import time
436
+ output_filename = f"quote_video_{int(time.time())}.mp4"
437
+ output_path = os.path.join(output_dir, output_filename)
438
+
439
+ creation_result = create_quote_video_tool(
440
+ video_result["video_url"],
441
+ quote,
442
+ output_path
443
+ )
444
+
445
+ if not creation_result["success"]:
446
+ status_log.append(f" ❌ {creation_result['message']}")
447
+ return "\n".join(status_log), video_result["video_url"], None
448
+
449
+ status_log.append(" βœ… Video created!\n")
450
  status_log.append("🎬 **COMPLETE!**")
451
 
452
+ return "\n".join(status_log), video_result["video_url"], creation_result["output_path"]
453
 
454
  # Gradio Interface
455
  with gr.Blocks(title="AIQuoteClipGenerator - MCP Edition", theme=gr.themes.Soft()) as demo:
 
459
 
460
  **MCP Integration Features:**
461
  - πŸ”— **MCP Server:** Connected to smolagents framework
462
+ - πŸ› οΈ **Custom Tools:** Quote generation + Video search + Video creation
463
  - πŸ€– **Agent Reasoning:** Autonomous task execution
464
  - ⚑ **Tool Orchestration:** Intelligent pipeline management
465
  """)
 
500
  output = gr.Textbox(label="Agent Status", lines=20, show_label=False)
501
 
502
  with gr.Row():
503
+ with gr.Column():
504
+ gr.Markdown("### πŸŽ₯ Background Video Preview")
505
+ preview_video = gr.Video(label="Original Pexels Video")
506
+
507
+ with gr.Column():
508
+ gr.Markdown("### ✨ Final Quote Video")
509
+ final_video = gr.Video(label="Download Your Video")
510
 
511
  gr.Markdown("""
512
  ---
513
  ### ✨ MCP Implementation
514
  - βœ… **smolagents Framework** - Proper MCP integration
515
+ - βœ… **Custom MCP Tools** - Quote generation, video search & video creation
516
  - βœ… **CodeAgent** - Autonomous reasoning and execution
517
  - βœ… **MCP Client** - Connected to external MCP servers
518
+ - βœ… **MoviePy Processing** - Text overlay with professional styling
519
+ - 🚧 **ElevenLabs Integration** - Voice narration (future)
520
 
521
  ### πŸ† Hackathon: MCP 1st Birthday
522
  **Track:** Track 2 - MCP in Action
523
  **Category:** Productivity Tools
524
+ **Built with:** Gradio + smolagents + OpenAI + Pexels + MoviePy + MCP
525
  """)
526
 
527
  generate_btn.click(
528
  mcp_agent_pipeline,
529
  inputs=[niche, style],
530
+ outputs=[output, preview_video, final_video]
531
  )
532
 
533
  if __name__ == "__main__":
534
+ demo.launch()