add caption hastag
Browse files
app.py
CHANGED
|
@@ -33,7 +33,7 @@ hybrid_quote_generator = HybridQuoteGenerator(
|
|
| 33 |
openai_client=openai_client,
|
| 34 |
)
|
| 35 |
|
| 36 |
-
# Initialize MCP Client (optional)
|
| 37 |
try:
|
| 38 |
mcp_client = MCPClient("https://abidlabs-mcp-tools.hf.space")
|
| 39 |
mcp_enabled = True
|
|
@@ -220,6 +220,62 @@ def get_trend_insights(niche: str) -> Dict[str, Any]:
|
|
| 220 |
return trends.get(niche, default)
|
| 221 |
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
# =============================================================================
|
| 224 |
# TOOLS
|
| 225 |
# =============================================================================
|
|
@@ -269,20 +325,6 @@ def search_pexels_video_tool(style: str, niche: str, trend_label: str = "") -> d
|
|
| 269 |
"""
|
| 270 |
Search and fetch a matching vertical video from Pexels based on style, niche,
|
| 271 |
and the current trend label.
|
| 272 |
-
|
| 273 |
-
Args:
|
| 274 |
-
style: Visual style for the background (e.g. Cinematic, Nature, Urban).
|
| 275 |
-
niche: Content niche (e.g. Motivation, Business/Entrepreneurship, Fitness).
|
| 276 |
-
trend_label: Short description of the trend theme used to bias search
|
| 277 |
-
queries (e.g. "soft life vs discipline era").
|
| 278 |
-
|
| 279 |
-
Returns:
|
| 280 |
-
A dictionary with:
|
| 281 |
-
- success: Whether any suitable video was found.
|
| 282 |
-
- video_url: Direct URL to an MP4 file (if success is True).
|
| 283 |
-
- search_query: The text query used for the Pexels search.
|
| 284 |
-
- pexels_url: Link to the Pexels video page.
|
| 285 |
-
- error: Error message if success is False.
|
| 286 |
"""
|
| 287 |
base_queries = {
|
| 288 |
"Motivation": {
|
|
@@ -405,19 +447,6 @@ def create_quote_video_tool(
|
|
| 405 |
) -> dict:
|
| 406 |
"""
|
| 407 |
Create the final quote video via the Modal web endpoint.
|
| 408 |
-
|
| 409 |
-
Args:
|
| 410 |
-
video_url: Direct URL to the background video file (MP4) from Pexels.
|
| 411 |
-
quote_text: The text of the quote to render on top of the video.
|
| 412 |
-
output_path: Local filesystem path where the rendered video will be saved.
|
| 413 |
-
audio_b64: Optional base64-encoded audio bytes for voice-over narration.
|
| 414 |
-
text_style: Layout style for the quote text (e.g. 'classic_center').
|
| 415 |
-
|
| 416 |
-
Returns:
|
| 417 |
-
A dictionary with:
|
| 418 |
-
- success: Whether video rendering succeeded.
|
| 419 |
-
- output_path: Path to the saved MP4 file if successful, else None.
|
| 420 |
-
- message: Human-readable status or error message.
|
| 421 |
"""
|
| 422 |
if not MODAL_ENDPOINT_URL:
|
| 423 |
return {
|
|
@@ -621,7 +650,11 @@ def mcp_agent_pipeline(
|
|
| 621 |
text_style: str,
|
| 622 |
voice_profile: str,
|
| 623 |
num_variations: int = 1,
|
| 624 |
-
) -> Tuple[str, List[str]]:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
status_log: List[str] = []
|
| 626 |
status_log.append("π€ **MCP-STYLE AGENT PIPELINE START**\n")
|
| 627 |
|
|
@@ -656,7 +689,7 @@ def mcp_agent_pipeline(
|
|
| 656 |
quote = generate_quote_tool(niche, style, persona)
|
| 657 |
if quote.startswith("Error"):
|
| 658 |
status_log.append(f" β Quote generation error: {quote}")
|
| 659 |
-
return "\n".join(status_log), []
|
| 660 |
|
| 661 |
preview = quote if len(quote) <= 140 else quote[:140] + "..."
|
| 662 |
status_log.append(f" β
Quote: β{preview}β\n")
|
|
@@ -695,7 +728,7 @@ def mcp_agent_pipeline(
|
|
| 695 |
|
| 696 |
if not video_results:
|
| 697 |
status_log.append("\nβ No background videos found. Aborting.")
|
| 698 |
-
return "\n".join(status_log), []
|
| 699 |
|
| 700 |
status_log.append("")
|
| 701 |
|
|
@@ -741,7 +774,7 @@ def mcp_agent_pipeline(
|
|
| 741 |
|
| 742 |
if not created_videos:
|
| 743 |
status_log.append("\nβ All video renderings failed.")
|
| 744 |
-
return "\n".join(status_log), []
|
| 745 |
|
| 746 |
status_log.append("\nπ **Integrations used:**")
|
| 747 |
status_log.append(" β’ Gemini β quote + variety tracking")
|
|
@@ -752,10 +785,16 @@ def mcp_agent_pipeline(
|
|
| 752 |
if mcp_enabled:
|
| 753 |
status_log.append(" β’ MCP server β available for extended tools")
|
| 754 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
status_log.append("\n⨠**Pipeline complete!**")
|
| 756 |
status_log.append(f" Generated {len(created_videos)} video variation(s).")
|
| 757 |
|
| 758 |
-
return "\n".join(status_log), created_videos
|
| 759 |
|
| 760 |
|
| 761 |
# =============================================================================
|
|
@@ -794,7 +833,7 @@ with gr.Blocks(
|
|
| 794 |
### MCP-style agent β’ Gemini + OpenAI + ElevenLabs + Modal
|
| 795 |
|
| 796 |
An autonomous mini-studio that generates trend-aware quote videos with voice-over,
|
| 797 |
-
cinematic stock footage, and
|
| 798 |
"""
|
| 799 |
)
|
| 800 |
|
|
@@ -880,11 +919,21 @@ with gr.Blocks(
|
|
| 880 |
show_label=False,
|
| 881 |
)
|
| 882 |
|
| 883 |
-
gr.Markdown("### β¨ Your Quote Videos
|
|
|
|
| 884 |
with gr.Row():
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
|
| 889 |
gr.Markdown(
|
| 890 |
"""
|
|
@@ -892,8 +941,8 @@ with gr.Blocks(
|
|
| 892 |
### π§© Under the hood
|
| 893 |
- Context engineering: niche + persona + trend theme
|
| 894 |
- Mini-RAG: curated trend knowledge feeding into generation
|
| 895 |
-
- Hybrid LLM: Gemini (quotes) + OpenAI (commentary)
|
| 896 |
-
- Multimodal pipeline: text β audio β video
|
| 897 |
"""
|
| 898 |
)
|
| 899 |
|
|
@@ -905,7 +954,7 @@ with gr.Blocks(
|
|
| 905 |
voice_profile_val,
|
| 906 |
num_variations_val,
|
| 907 |
):
|
| 908 |
-
status, videos = mcp_agent_pipeline(
|
| 909 |
niche=niche_val,
|
| 910 |
style=style_val,
|
| 911 |
persona=persona_val,
|
|
@@ -926,7 +975,7 @@ with gr.Blocks(
|
|
| 926 |
g5 = gallery_vids[4] if len(gallery_vids) > 4 else None
|
| 927 |
g6 = gallery_vids[5] if len(gallery_vids) > 5 else None
|
| 928 |
|
| 929 |
-
return status, v1, v2, v3, g1, g2, g3, g4, g5, g6
|
| 930 |
|
| 931 |
generate_btn.click(
|
| 932 |
process_and_display,
|
|
@@ -943,6 +992,7 @@ with gr.Blocks(
|
|
| 943 |
video1,
|
| 944 |
video2,
|
| 945 |
video3,
|
|
|
|
| 946 |
gallery_video1,
|
| 947 |
gallery_video2,
|
| 948 |
gallery_video3,
|
|
@@ -971,5 +1021,4 @@ with gr.Blocks(
|
|
| 971 |
)
|
| 972 |
|
| 973 |
if __name__ == "__main__":
|
| 974 |
-
demo.launch(allowed_paths=["/data/gallery_videos"])
|
| 975 |
-
|
|
|
|
| 33 |
openai_client=openai_client,
|
| 34 |
)
|
| 35 |
|
| 36 |
+
# Initialize MCP Client (optional, not critical if missing)
|
| 37 |
try:
|
| 38 |
mcp_client = MCPClient("https://abidlabs-mcp-tools.hf.space")
|
| 39 |
mcp_enabled = True
|
|
|
|
| 220 |
return trends.get(niche, default)
|
| 221 |
|
| 222 |
|
| 223 |
+
# =============================================================================
|
| 224 |
+
# CAPTION + HASHTAG GENERATION (non-MCP version)
|
| 225 |
+
# =============================================================================
|
| 226 |
+
|
| 227 |
+
def generate_caption_and_hashtags(niche: str, persona: str, trend_label: str) -> str:
|
| 228 |
+
"""
|
| 229 |
+
Generate a posting-ready caption, hashtags, and a tiny posting tip
|
| 230 |
+
based on niche + persona + trend theme.
|
| 231 |
+
"""
|
| 232 |
+
persona_instruction = get_persona_instruction(persona)
|
| 233 |
+
|
| 234 |
+
prompt = f"""
|
| 235 |
+
Generate a social-media-ready caption and hashtags for a short vertical quote video.
|
| 236 |
+
|
| 237 |
+
Niche: {niche}
|
| 238 |
+
Persona / tone: {persona} ({persona_instruction})
|
| 239 |
+
Trend theme: {trend_label}
|
| 240 |
+
|
| 241 |
+
Requirements:
|
| 242 |
+
- CAPTION: 1β2 sentences max
|
| 243 |
+
* Should sound natural, like a human writing for TikTok/Instagram
|
| 244 |
+
* Should NOT repeat the quote text word-for-word
|
| 245 |
+
* Can reference feelings, situation, or transformation implied by the quote
|
| 246 |
+
- HASHTAGS:
|
| 247 |
+
* 8β12 hashtags total
|
| 248 |
+
* Mix of trending-style tags and niche / long-tail tags
|
| 249 |
+
* Use lowercase, no spaces (standard hashtag conventions)
|
| 250 |
+
* No banned, misleading, or spammy tags
|
| 251 |
+
- POSTING TIP:
|
| 252 |
+
* 1 short sentence with a practical suggestion (sound choice, posting time, or CTA)
|
| 253 |
+
|
| 254 |
+
Format the answer EXACTLY like this:
|
| 255 |
+
|
| 256 |
+
CAPTION:
|
| 257 |
+
<caption text>
|
| 258 |
+
|
| 259 |
+
HASHTAGS:
|
| 260 |
+
#tag1 #tag2 #tag3 ...
|
| 261 |
+
|
| 262 |
+
POSTING TIP:
|
| 263 |
+
<one short tip>
|
| 264 |
+
"""
|
| 265 |
+
|
| 266 |
+
try:
|
| 267 |
+
completion = openai_client.chat.completions.create(
|
| 268 |
+
model="gpt-4o-mini",
|
| 269 |
+
messages=[{"role": "user", "content": prompt}],
|
| 270 |
+
max_tokens=220,
|
| 271 |
+
temperature=0.8,
|
| 272 |
+
)
|
| 273 |
+
text = completion.choices[0].message.content.strip()
|
| 274 |
+
return text
|
| 275 |
+
except Exception as e:
|
| 276 |
+
return f"Error generating caption/hashtags: {str(e)}"
|
| 277 |
+
|
| 278 |
+
|
| 279 |
# =============================================================================
|
| 280 |
# TOOLS
|
| 281 |
# =============================================================================
|
|
|
|
| 325 |
"""
|
| 326 |
Search and fetch a matching vertical video from Pexels based on style, niche,
|
| 327 |
and the current trend label.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
"""
|
| 329 |
base_queries = {
|
| 330 |
"Motivation": {
|
|
|
|
| 447 |
) -> dict:
|
| 448 |
"""
|
| 449 |
Create the final quote video via the Modal web endpoint.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
"""
|
| 451 |
if not MODAL_ENDPOINT_URL:
|
| 452 |
return {
|
|
|
|
| 650 |
text_style: str,
|
| 651 |
voice_profile: str,
|
| 652 |
num_variations: int = 1,
|
| 653 |
+
) -> Tuple[str, List[str], str]:
|
| 654 |
+
"""
|
| 655 |
+
Returns:
|
| 656 |
+
status_log_str, created_videos, caption_block
|
| 657 |
+
"""
|
| 658 |
status_log: List[str] = []
|
| 659 |
status_log.append("π€ **MCP-STYLE AGENT PIPELINE START**\n")
|
| 660 |
|
|
|
|
| 689 |
quote = generate_quote_tool(niche, style, persona)
|
| 690 |
if quote.startswith("Error"):
|
| 691 |
status_log.append(f" β Quote generation error: {quote}")
|
| 692 |
+
return "\n".join(status_log), [], ""
|
| 693 |
|
| 694 |
preview = quote if len(quote) <= 140 else quote[:140] + "..."
|
| 695 |
status_log.append(f" β
Quote: β{preview}β\n")
|
|
|
|
| 728 |
|
| 729 |
if not video_results:
|
| 730 |
status_log.append("\nβ No background videos found. Aborting.")
|
| 731 |
+
return "\n".join(status_log), [], ""
|
| 732 |
|
| 733 |
status_log.append("")
|
| 734 |
|
|
|
|
| 774 |
|
| 775 |
if not created_videos:
|
| 776 |
status_log.append("\nβ All video renderings failed.")
|
| 777 |
+
return "\n".join(status_log), [], ""
|
| 778 |
|
| 779 |
status_log.append("\nπ **Integrations used:**")
|
| 780 |
status_log.append(" β’ Gemini β quote + variety tracking")
|
|
|
|
| 785 |
if mcp_enabled:
|
| 786 |
status_log.append(" β’ MCP server β available for extended tools")
|
| 787 |
|
| 788 |
+
# Step 6 β Caption + hashtags block (returned separately for the UI)
|
| 789 |
+
status_log.append(
|
| 790 |
+
"\nπ **Step 6 β Caption + Hashtags** (see the panel next to your videos to copy-paste)"
|
| 791 |
+
)
|
| 792 |
+
caption_block = generate_caption_and_hashtags(niche, persona, trend_label)
|
| 793 |
+
|
| 794 |
status_log.append("\n⨠**Pipeline complete!**")
|
| 795 |
status_log.append(f" Generated {len(created_videos)} video variation(s).")
|
| 796 |
|
| 797 |
+
return "\n".join(status_log), created_videos, caption_block
|
| 798 |
|
| 799 |
|
| 800 |
# =============================================================================
|
|
|
|
| 833 |
### MCP-style agent β’ Gemini + OpenAI + ElevenLabs + Modal
|
| 834 |
|
| 835 |
An autonomous mini-studio that generates trend-aware quote videos with voice-over,
|
| 836 |
+
cinematic stock footage, and ready-to-post captions + hashtags.
|
| 837 |
"""
|
| 838 |
)
|
| 839 |
|
|
|
|
| 919 |
show_label=False,
|
| 920 |
)
|
| 921 |
|
| 922 |
+
gr.Markdown("### β¨ Your Quote Videos & Caption")
|
| 923 |
+
|
| 924 |
with gr.Row():
|
| 925 |
+
with gr.Column(scale=3):
|
| 926 |
+
with gr.Row():
|
| 927 |
+
video1 = gr.Video(label="Video 1", height=420)
|
| 928 |
+
video2 = gr.Video(label="Video 2", height=420)
|
| 929 |
+
video3 = gr.Video(label="Video 3", height=420)
|
| 930 |
+
with gr.Column(scale=2):
|
| 931 |
+
caption_box = gr.Textbox(
|
| 932 |
+
label="π Caption + Hashtags + Posting Tip",
|
| 933 |
+
lines=14,
|
| 934 |
+
show_label=True,
|
| 935 |
+
interactive=False,
|
| 936 |
+
)
|
| 937 |
|
| 938 |
gr.Markdown(
|
| 939 |
"""
|
|
|
|
| 941 |
### π§© Under the hood
|
| 942 |
- Context engineering: niche + persona + trend theme
|
| 943 |
- Mini-RAG: curated trend knowledge feeding into generation
|
| 944 |
+
- Hybrid LLM: Gemini (quotes) + OpenAI (commentary & captions)
|
| 945 |
+
- Multimodal pipeline: text β audio β video β posting assets
|
| 946 |
"""
|
| 947 |
)
|
| 948 |
|
|
|
|
| 954 |
voice_profile_val,
|
| 955 |
num_variations_val,
|
| 956 |
):
|
| 957 |
+
status, videos, caption_block = mcp_agent_pipeline(
|
| 958 |
niche=niche_val,
|
| 959 |
style=style_val,
|
| 960 |
persona=persona_val,
|
|
|
|
| 975 |
g5 = gallery_vids[4] if len(gallery_vids) > 4 else None
|
| 976 |
g6 = gallery_vids[5] if len(gallery_vids) > 5 else None
|
| 977 |
|
| 978 |
+
return status, v1, v2, v3, caption_block, g1, g2, g3, g4, g5, g6
|
| 979 |
|
| 980 |
generate_btn.click(
|
| 981 |
process_and_display,
|
|
|
|
| 992 |
video1,
|
| 993 |
video2,
|
| 994 |
video3,
|
| 995 |
+
caption_box,
|
| 996 |
gallery_video1,
|
| 997 |
gallery_video2,
|
| 998 |
gallery_video3,
|
|
|
|
| 1021 |
)
|
| 1022 |
|
| 1023 |
if __name__ == "__main__":
|
| 1024 |
+
demo.launch(allowed_paths=["/data/gallery_videos"])
|
|
|