Add AI marketing and writing tools from PRs #220, #310

New tools added to ToBeMigrated/ directory:

ai_marketing_tools/:
- ai_backlinker: AI-powered backlink generation
- ai_google_ads_generator: Google Ads generation with templates

ai_writers/:
- ai_blog_faqs_writer: FAQ generation for blogs
- ai_copywriter: Multiple copywriter frameworks (AIDA, PAS, 4C, 4R, etc.)
- ai_finance_report_generator: Financial report generation
- ai_story_illustrator: Story illustration
- ai_story_video_generator: Story video generation
- ai_story_writer: AI story writing
- github_blogs: GitHub blog integration
- speech_to_blog: Audio to blog conversion
- twitter_writers: Twitter/X content generation
- youtube_writers: YouTube content generation

These tools are in ToBeMigrated/ for future migration to the main backend.
This commit is contained in:
ajaysi
2026-03-22 11:47:21 +05:30
parent 1fd9720dac
commit 3c58fd555b
91 changed files with 26451 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
# YouTube Thumbnail Generator
A powerful AI-powered tool for creating engaging, click-worthy thumbnails for your YouTube videos.
## Overview
The YouTube Thumbnail Generator is a specialized module within the AI Writer suite that helps content creators design eye-catching thumbnails optimized for YouTube. Using advanced AI image generation technology, this tool creates custom thumbnails based on your video content, target audience, and style preferences.
## Features
### 1. AI-Powered Thumbnail Generation
- **Concept Generation**: Automatically generates multiple thumbnail concept ideas based on your video title, description, and target audience
- **Visual Design**: Creates high-quality thumbnail images using state-of-the-art AI image generation
- **Style Customization**: Choose from various style preferences including bold, clean, colorful, dark, professional, playful, retro, and modern
### 2. Advanced Customization Options
- **Aspect Ratio Selection**: Choose from standard YouTube ratios (16:9, 1:1, 4:3, 9:16)
- **Text Overlay Options**: Add and customize text overlays with different styles
- **Image Style Selection**: Choose from photorealistic, artistic, cartoon/anime, sketch/drawing, digital art, or 3D render
- **Focus Selection**: For photorealistic images, specify focus areas like portraits, objects, motion, or wide-angle
### 3. Thumbnail Editing
- **AI-Powered Editing**: Make changes to your generated thumbnails using natural language instructions
- **Iterative Refinement**: Continue editing until you're satisfied with the result
- **Preserve Original**: Keep both original and edited versions of your thumbnails
### 4. Thumbnail Analysis
- **AI Analysis**: Get feedback on your thumbnail's effectiveness
- **Improvement Suggestions**: Receive specific recommendations to enhance your thumbnail's impact
- **Best Practices**: Learn about visual hierarchy, text readability, emotional impact, and click-worthiness
### 5. User-Friendly Interface
- **Tabbed Interface**: Organize your workflow with intuitive tabs for basic info and style settings
- **Concept Tabs**: View and select from multiple thumbnail concepts
- **Real-time Preview**: See your generated thumbnails immediately
- **Download Options**: Easily download your thumbnails in high resolution
## How to Use
### Step 1: Enter Basic Information
- Provide your video title and description
- Specify your target audience
- Select your content type (tutorial, vlog, review, etc.)
### Step 2: Customize Style Preferences
- Choose your preferred thumbnail style
- Select the number of concepts to generate
- Pick your desired aspect ratio
- Configure text overlay options
### Step 3: Generate Thumbnail Concepts
- Click "Generate Thumbnail Concepts" to create multiple thumbnail ideas
- Review each concept in the provided tabs
- Select the concept you'd like to develop further
### Step 4: Generate and Customize Your Thumbnail
- Click "Generate Image" for your selected concept
- Use the editing tools to refine your thumbnail
- Apply changes using natural language instructions
- Download your final thumbnail when satisfied
### Step 5: Analyze Your Thumbnail
- Use the "Analyze Thumbnail" feature to get AI feedback
- Review suggestions for improvement
- Make additional edits based on the analysis
## Technical Details
The Thumbnail Generator uses:
- **Gemini AI**: For high-quality image generation and editing
- **Advanced Prompt Engineering**: To ensure consistent and relevant results
- **Retry Mechanism**: Handles service overloads with exponential backoff
- **Session State Management**: Preserves your work across page refreshes
## Tips for Best Results
1. **Be Specific**: Provide detailed video descriptions to help the AI understand your content
2. **Target Your Audience**: Specify your audience demographics and interests
3. **Choose Appropriate Style**: Select a style that matches your channel's branding
4. **Use Keywords**: Add relevant keywords to enhance the AI's understanding
5. **Iterate**: Don't hesitate to generate multiple concepts and make edits
6. **Analyze**: Use the analysis feature to get objective feedback on your thumbnails
## Requirements
- Internet connection for AI services
- Modern web browser
- No additional software installation required
## Support
For technical issues or feature requests, please contact our support team or submit an issue on our GitHub repository.
---
*The YouTube Thumbnail Generator is part of the AI Writer suite, designed to help content creators streamline their workflow and produce high-quality content.*

View File

@@ -0,0 +1,108 @@
End Screen Generator feature for YouTube videos.
## Step 1: Understanding End Screens
YouTube end screens are the final elements shown at the end of a video that encourage viewers to take action, such as subscribing, watching another video, or visiting a website. They typically include:
1. Call-to-action elements (subscribe button, playlists, other videos)
2. Visual elements (background image, branding)
3. Text overlays (promotional messages, channel name)
4. Layout options (different templates for different purposes)
## Step 2: Required User Inputs
Based on the thumbnail generator and YouTube end screen requirements, we'll need these inputs:
1. **Basic Video Information**:
- Video title
- Video description
- Target audience
- Content type (tutorial, vlog, review, etc.)
2. **End Screen Purpose**:
- Primary goal (drive subscriptions, promote playlist, promote next video, etc.)
- Secondary goal (if applicable)
3. **Visual Style Preferences**:
- Color scheme
- Style (minimal, bold, branded, etc.)
- Brand elements to include (logo, channel name, etc.)
4. **Content Elements**:
- Number of elements to include (1-4)
- Types of elements (subscribe button, playlist, video, website)
- Text for each element
5. **Advanced Settings**:
- Background style (solid color, gradient, image, etc.)
- Animation preferences
- Custom branding elements
## Step 3: Implementation Plan
Let's create a new module called `end_screen_generator.py` in the same directory as the thumbnail generator. Here's how we'll structure it:
1. **Functions**:
- `generate_end_screen_concepts`: Generate end screen design concepts
- `generate_end_screen_design`: Create visual end screen designs
- `analyze_end_screen`: Provide feedback on end screen effectiveness
- `write_yt_end_screen`: Main UI function
2. **User Interface**:
- Tabs for different sections (Basic Info, Style & Elements, Preview)
- Input fields for all required information
- Preview section to show generated end screens
- Download options for the end screen designs
### End Screen Generator Features
1. **Comprehensive User Inputs**:
- Basic video information (title, description, target audience)
- End screen purpose (subscribe, next video, playlist, website, social media)
- Visual style preferences (modern, minimalist, bold, playful, elegant)
- Content elements (text, CTAs, visual elements)
- Advanced settings (image style, focus, keywords)
2. **AI-Powered Generation**:
- Concept generation with detailed descriptions
- Image generation with style customization
- Thumbnail analysis for effectiveness
- Image editing capabilities
3. **User Interface**:
- Tabbed interface for multiple end screen concepts
- Visual preview of generated end screens
- Download options for all generated images
- Edit functionality for refining designs
4. **Integration with Existing Tools**:
- Reuses the image generation and editing functions from the thumbnail generator
- Consistent UI/UX with other YouTube tools
- Proper error handling and logging
### How to Use the End Screen Generator
1. **Access the Tool**:
- Select "End Screen Generator" from the YouTube tools menu
- The tool is now active and ready to use
2. **Generate End Screens**:
- Enter your video details (title, description, target audience)
- Select the primary purpose of your end screen
- Choose your preferred visual style
- Select content elements to include
- Optionally customize advanced settings
- Click "Generate End Screen Concepts"
3. **Review and Customize**:
- Browse through the generated concepts in tabs
- Generate images for concepts you like
- Edit the generated images with specific instructions
- Download your final end screen designs
4. **Analyze Effectiveness**:
- Get AI-powered analysis of your end screen designs
- Receive feedback on visual hierarchy, text readability, and more
The End Screen Generator is now fully integrated into the YouTube AI Writer and ready to use. Would you like me to make any adjustments or enhancements to the implementation?

View File

@@ -0,0 +1,273 @@
# YouTube Shorts Script Generator 📱
Welcome to the ultimate YouTube Shorts Script Generator! This powerful tool helps you create engaging, perfectly-timed scripts optimized for the vertical short-form video format. Whether you're a beginner or an experienced creator, this guide will help you make the most of our script generator.
## 🎯 Why Use This Tool?
- Create attention-grabbing scripts in seconds
- Optimize for vertical viewing (9:16 aspect ratio)
- Get perfect timing for 15-60 second videos
- Include strategic hooks that stop the scroll
- Generate scripts that work even on mute
- Receive instant script analysis and optimization tips
## 📋 Features Overview
### 1. Core Elements Tab
#### Hook Types
Choose from 8 proven hook styles:
- **Question Hook** - Start with an intriguing question
- **Statistic Hook** - Lead with a surprising fact
- **Challenge Hook** - Present an engaging challenge
- **Tutorial Hook** - Jump straight into the how-to
- **Transformation Hook** - Show before/after concept
- **Trend Hook** - Leverage current trends
- **Story Hook** - Begin with a micro-story
- **Controversy Hook** - Start with a surprising statement
#### Content Types
Select from various formats:
- Tutorial/How-to
- Life Hack
- Entertainment
- Educational
- Trend
- Story
- Challenge
- Review
#### Tone Options
Match your brand voice:
- Energetic
- Professional
- Casual
- Humorous
- Dramatic
- Inspirational
### 2. Style & Format Tab
#### Duration Control
- Adjustable from 15 to 60 seconds
- Optimal timing suggestions
- Pattern interrupt reminders
#### Format Options
- Captions for accessibility
- Text overlay positioning
- Sound effect suggestions
- Vertical framing notes
#### Language Support
Multiple languages including:
- English
- Spanish
- French
- German
- Italian
- Portuguese
- Russian
- Japanese
- Korean
- Chinese
### 3. Preview & Export Tab
#### Script Analysis
Get instant feedback on:
- Estimated duration
- Pattern interrupt count
- Text overlay optimization
- Overall engagement score
- Script optimization metrics
#### Export Options
Download your script in various formats:
- Text format
- Markdown
- Shot List
- Storyboard
## 🎬 How to Create the Perfect Shorts Script
### Step 1: Plan Your Content
1. **Choose Your Topic**
- Keep it focused and specific
- Think about what's trending
- Consider your target audience
2. **Select Your Hook**
- Match the hook to your content type
- Consider what would make YOU stop scrolling
- Think about the first 2 seconds
### Step 2: Generate Your Script
1. Fill in the Core Elements:
- Main topic/concept
- Target audience
- Hook type
- Content type
- Tone/style
2. Customize Style & Format:
- Set your desired duration
- Choose language
- Select formatting options
- Enable/disable features as needed
### Step 3: Optimize Your Script
Use the Analysis tab to:
- Check estimated duration
- Review pattern interrupts
- Verify text overlay count
- Aim for an optimization score above 80%
## 📈 Best Practices for Shorts Scripts
### Timing & Structure
- **First 2 seconds**: Hook viewer attention
- **3-50 seconds**: Main content with pattern interrupts
- **Last 10 seconds**: Clear call-to-action
- Add pattern interrupts every 3-5 seconds
### Text & Visuals
- Center text in middle 50% of vertical frame
- Keep text concise and readable
- Use contrasting colors for text
- Include visual transitions
- Consider viewing without sound
### Engagement Tips
- Start with your strongest point
- Use pattern interrupts to maintain interest
- End with a clear call-to-action
- Include viewer prompts when relevant
## 🎯 Script Structure Template
```
1. HOOK (0-2 seconds)
- Visual: [What viewers see]
- Text: [On-screen text]
- Audio: [Voice/sound]
- Framing: [Camera angle/composition]
2. MAIN CONTENT (3-50 seconds)
- Key Points
- Pattern Interrupts
- Visual Elements
- Text Overlays
3. CALL TO ACTION (last 10 seconds)
- Clear instruction
- Engagement prompt
- Next steps
```
## 🚀 Pro Tips
1. **Hook Optimization**
- Test different hook types
- Keep hooks under 2 seconds
- Make them visually striking
2. **Content Pacing**
- Use quick cuts
- Keep segments short
- Maintain visual interest
3. **Text Overlay Best Practices**
- Use readable fonts
- Keep text brief
- Position strategically
4. **Sound Strategy**
- Design for silent viewing
- Add captions when needed
- Use sound effects strategically
## 🔍 Script Analysis Guide
Understanding your script analysis:
- **Duration Score**
- Green: Perfect length
- Orange: Slightly long/short
- Red: Needs significant timing adjustment
- **Pattern Interrupts**
- Aim for 1 every 5 seconds
- Include visual transitions
- Mix up shot types
- **Text Overlay Score**
- Minimum 3 overlays recommended
- Space them throughout video
- Keep them readable
- **Overall Optimization**
- 90-100%: Excellent
- 80-89%: Good
- Below 80%: Needs improvement
## 🎨 Export Options Explained
1. **Text Format**
- Clean, simple script
- Easy to copy/paste
- Basic formatting
2. **Markdown**
- Formatted sections
- Easy to read
- Good for documentation
3. **Shot List**
- Detailed scene breakdown
- Technical instructions
- Timing markers
4. **Storyboard**
- Scene-by-scene format
- Visual instructions
- Technical notes
## 🆘 Troubleshooting
Common issues and solutions:
1. **Script Too Long**
- Reduce main points
- Shorten sentences
- Speed up pacing
2. **Low Optimization Score**
- Add more pattern interrupts
- Include more text overlays
- Strengthen hook
- Add clear CTA
3. **Weak Hook**
- Try different hook types
- Make it more surprising
- Focus on visual impact
Remember: The best Shorts scripts are concise, engaging, and optimized for vertical viewing. Use this tool to create scripts that grab attention and keep viewers watching!
## 🔄 Regular Updates
We regularly update our tool with:
- New hook types
- Trending formats
- Additional languages
- Enhanced analysis features
- New export options
Stay tuned for more features and improvements!
---
Happy Creating! 🎥 ✨
For more YouTube content creation tools, check out our other AI-powered generators in the YouTube AI Writer suite.

View File

@@ -0,0 +1,5 @@
"""
YouTube AI Writer Modules
This package contains modular components for the YouTube AI Writer functionality.
"""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,591 @@
"""
YouTube Community Post Generator Module
This module provides sophisticated functionality for generating engaging community posts
with AI-powered content suggestions, engagement analysis, and timing optimization.
"""
import streamlit as st
import time
import logging
import random
from datetime import datetime, timedelta
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
import re
from textblob import TextBlob
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('youtube_community_post_generator')
def generate_community_post(post_type, main_topic, target_audience, tone_style,
content_purpose, channel_niche, include_emoji=True,
include_hashtags=True, include_poll=False,
include_image_prompt=False, include_timing_suggestion=True,
max_length=None, language="English"):
"""Generate an AI-optimized community post with engagement features."""
# Create a custom system prompt for community post generation
system_prompt = f"""You are a YouTube Community Post expert specializing in creating highly engaging,
conversion-optimized posts that drive channel growth and viewer interaction.
Focus on creating posts that encourage meaningful engagement while maintaining the channel's voice.
Write the entire post in {language}.
Consider timing, audience psychology, and platform-specific best practices."""
# Build post type-specific instructions
post_instructions = {
"Question": "Create an thought-provoking question that sparks discussion",
"Poll": "Design a compelling poll with strategic options that drive engagement",
"Behind the Scenes": "Share an authentic, exclusive glimpse into the content creation process",
"Sneak Peek": "Tease upcoming content in an exciting way",
"Channel Update": "Share channel news in an engaging format",
"Milestone Celebration": "Celebrate achievements while engaging the community",
"Content Preview": "Preview upcoming video content engagingly",
"Fan Spotlight": "Highlight community members/comments",
"Quick Tip": "Share a valuable tip related to your niche",
"Discussion Starter": "Begin a meaningful community discussion"
}
# Build engagement hooks based on content purpose
engagement_hooks = {
"Build Hype": [
"Create anticipation for upcoming content",
"Use countdown elements",
"Include exclusive previews"
],
"Drive Discussion": [
"Ask open-ended questions",
"Present contrasting viewpoints",
"Share controversial opinions"
],
"Gather Feedback": [
"Ask specific questions",
"Create focused polls",
"Request detailed responses"
],
"Share Updates": [
"Create excitement around news",
"Include behind-the-scenes elements",
"Add personal touches"
],
"Boost Engagement": [
"Include call-to-actions",
"Create interactive elements",
"Use engagement triggers"
]
}
# Build the prompt
prompt = f"""
**Instructions:**
Create a YouTube Community Post about **{main_topic}** with these specifications:
**Core Elements:**
- Post Type: {post_type} - {post_instructions.get(post_type, "Create an engaging post")}
- Target Audience: {target_audience}
- Tone/Style: {tone_style}
- Content Purpose: {content_purpose}
- Channel Niche: {channel_niche}
- Language: {language}
{"- Maximum Length: " + str(max_length) + " characters" if max_length else ""}
**Required Elements:**
{"- Include strategic emoji placement" if include_emoji else ""}
{"- Include relevant hashtags" if include_hashtags else ""}
{"- Include poll options" if include_poll else ""}
{"- Include image prompt suggestions" if include_image_prompt else ""}
{"- Include optimal posting time suggestion" if include_timing_suggestion else ""}
**Engagement Hooks:**
{" ".join(engagement_hooks.get(content_purpose, ["Create engaging content"]))}
**Format the post with:**
1. Main Content
2. Engagement Elements
3. Call-to-Action
4. Additional Components (hashtags, etc.)
**Remember:**
- Keep the tone consistent with channel voice
- Use psychology triggers for engagement
- Include clear call-to-actions
- Make it easy to respond to
- Create shareable content
"""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response
except Exception as err:
st.error(f"Error: Failed to get response from LLM: {err}")
return None
def analyze_post_engagement(post_content):
"""Analyze a community post for engagement potential using advanced AI metrics."""
analysis = {
'engagement_score': 0,
'emotional_triggers': 0,
'call_to_action_strength': 0,
'readability_score': 0,
'hashtag_optimization': 0,
'timing_recommendation': None,
'sentiment_analysis': {},
'virality_potential': 0,
'audience_resonance': 0,
'content_uniqueness': 0,
'psychological_triggers': [],
'improvement_suggestions': [],
'engagement_patterns': {},
'content_structure': {},
'seo_optimization': 0
}
# Sentiment Analysis using TextBlob
blob = TextBlob(post_content)
analysis['sentiment_analysis'] = {
'polarity': round((blob.sentiment.polarity + 1) * 50, 2), # Convert to 0-100 scale
'subjectivity': round(blob.sentiment.subjectivity * 100, 2),
'tone': 'Positive' if blob.sentiment.polarity > 0 else 'Negative' if blob.sentiment.polarity < 0 else 'Neutral'
}
# Analyze emotional triggers with expanded vocabulary
emotional_categories = {
'excitement': ['excited', 'amazing', 'incredible', 'awesome', 'mind-blowing'],
'curiosity': ['guess what', 'secret', 'revealed', 'discover', 'mystery'],
'urgency': ['limited', 'hurry', 'soon', 'don\'t miss', 'last chance'],
'social_proof': ['everyone', 'community', 'fans', 'you all', 'together'],
'exclusivity': ['exclusive', 'special', 'limited', 'only', 'selected']
}
trigger_counts = {category: 0 for category in emotional_categories}
for category, words in emotional_categories.items():
trigger_counts[category] = sum(post_content.lower().count(word) for word in words)
analysis['emotional_triggers'] = min(sum(trigger_counts.values()) * 15, 100)
analysis['psychological_triggers'] = [cat for cat, count in trigger_counts.items() if count > 0]
# Analyze call-to-action strength with pattern recognition
cta_patterns = {
'question_cta': r'\?',
'direct_command': r'(?i)(comment|share|like|subscribe|follow)',
'engagement_request': r'(?i)(let (me|us) know|tell (me|us)|what do you think)',
'time_sensitive': r'(?i)(today|now|limited time|hurry)',
'value_proposition': r'(?i)(learn|discover|find out|get|access)'
}
cta_strength = 0
for pattern_type, pattern in cta_patterns.items():
matches = len(re.findall(pattern, post_content))
cta_strength += matches * 20
analysis['call_to_action_strength'] = min(cta_strength, 100)
# Content Structure Analysis
analysis['content_structure'] = {
'length_score': min(len(post_content.split()) / 5, 100), # Optimal length analysis
'paragraph_breaks': min(post_content.count('\n\n') * 20, 100), # Readability through structure
'emoji_balance': min(len(re.findall(r'[\U0001F300-\U0001F9FF]', post_content)) * 10, 100), # Emoji usage score
'formatting_score': min((post_content.count('*') + post_content.count('_')) * 5, 100) # Text formatting score
}
# Virality Potential Analysis
virality_factors = {
'emotional_impact': analysis['emotional_triggers'],
'shareability': analysis['content_structure']['length_score'],
'uniqueness': random.randint(60, 100), # Simulated uniqueness score
'timeliness': 80 if any(word in post_content.lower() for word in ['new', 'breaking', 'update', 'just']) else 50
}
analysis['virality_potential'] = sum(virality_factors.values()) / len(virality_factors)
# Audience Resonance Analysis
resonance_factors = {
'relevance': analysis['sentiment_analysis']['subjectivity'],
'engagement_hooks': analysis['call_to_action_strength'],
'emotional_connection': analysis['emotional_triggers']
}
analysis['audience_resonance'] = sum(resonance_factors.values()) / len(resonance_factors)
# SEO Optimization
seo_factors = {
'hashtag_quality': analyze_hashtag_quality(post_content),
'keyword_density': analyze_keyword_density(post_content),
'url_presence': 100 if 'http' in post_content else 0,
'mention_optimization': analyze_mentions(post_content)
}
analysis['seo_optimization'] = sum(seo_factors.values()) / len(seo_factors)
# Engagement Pattern Analysis
analysis['engagement_patterns'] = analyze_engagement_patterns(post_content)
# Calculate overall engagement score with weighted components
analysis['engagement_score'] = calculate_weighted_score({
'emotional_triggers': (analysis['emotional_triggers'], 0.2),
'call_to_action_strength': (analysis['call_to_action_strength'], 0.2),
'virality_potential': (analysis['virality_potential'], 0.15),
'audience_resonance': (analysis['audience_resonance'], 0.15),
'seo_optimization': (analysis['seo_optimization'], 0.1),
'sentiment_balance': (analysis['sentiment_analysis']['polarity'], 0.1),
'content_structure': (sum(analysis['content_structure'].values()) / len(analysis['content_structure']), 0.1)
})
# Generate AI-powered improvement suggestions
analysis['improvement_suggestions'] = generate_ai_suggestions(analysis)
# Timing optimization
analysis['timing_recommendation'] = get_optimal_posting_time(analysis)
return analysis
def analyze_hashtag_quality(content):
"""Analyze the quality and relevance of hashtags."""
hashtags = re.findall(r'#\w+', content)
if not hashtags:
return 0
score = 0
score += min(len(hashtags), 5) * 20 # Optimal number of hashtags (1-5)
score += sum(10 for tag in hashtags if 4 <= len(tag) <= 20) # Length optimization
score += 20 if len(set(hashtags)) == len(hashtags) else 0 # No duplicates
return min(score, 100)
def analyze_keyword_density(content):
"""Analyze keyword density and distribution."""
words = content.lower().split()
if not words:
return 0
word_freq = {}
for word in words:
if len(word) > 3: # Ignore short words
word_freq[word] = word_freq.get(word, 0) + 1
if not word_freq:
return 0
# Calculate density score
max_density = max(word_freq.values()) / len(words)
return 100 if 0.01 <= max_density <= 0.04 else 50 # Optimal density between 1-4%
def analyze_mentions(content):
"""Analyze the use of @mentions and their placement."""
mentions = re.findall(r'@\w+', content)
if not mentions:
return 0
score = 0
score += min(len(mentions), 3) * 25 # Optimal number of mentions (1-3)
score += 25 if mentions[0] in content.split()[:len(content.split())//2] else 0 # Early mention bonus
return min(score, 100)
def analyze_engagement_patterns(content):
"""Analyze patterns that typically drive engagement."""
patterns = {
'question_hooks': len(re.findall(r'\?', content)),
'emotional_words': len(re.findall(r'\b(love|hate|amazing|awesome|incredible|excited)\b', content.lower())),
'community_references': len(re.findall(r'\b(we|our|community|together|everyone)\b', content.lower())),
'action_words': len(re.findall(r'\b(get|do|make|try|click|watch|share)\b', content.lower())),
'urgency_triggers': len(re.findall(r'\b(now|today|limited|soon|hurry)\b', content.lower()))
}
return {k: min(v * 20, 100) for k, v in patterns.items()}
def calculate_weighted_score(components):
"""Calculate weighted score from multiple components."""
return sum(score * weight for (score, weight) in components.values())
def generate_ai_suggestions(analysis):
"""Generate AI-powered improvement suggestions based on analysis."""
suggestions = []
if analysis['emotional_triggers'] < 70:
suggestions.append({
'category': 'Emotional Impact',
'suggestion': 'Add more emotional triggers to increase engagement',
'examples': ['amazing', 'incredible', 'exciting']
})
if analysis['call_to_action_strength'] < 70:
suggestions.append({
'category': 'Call-to-Action',
'suggestion': 'Strengthen your call-to-action',
'examples': ['Comment below', 'Share your thoughts', 'Let me know']
})
if analysis['virality_potential'] < 70:
suggestions.append({
'category': 'Virality',
'suggestion': 'Increase viral potential by adding trending elements',
'examples': ['Current trends', 'Popular hashtags', 'Timely topics']
})
if analysis['seo_optimization'] < 70:
suggestions.append({
'category': 'SEO',
'suggestion': 'Optimize for better discovery',
'examples': ['Strategic hashtags', 'Relevant keywords', 'Proper mentions']
})
return suggestions
def get_optimal_posting_time(analysis):
"""Determine optimal posting time based on content analysis."""
current_hour = datetime.now().hour
# Factor in content type and engagement patterns
if analysis['sentiment_analysis']['tone'] == 'Positive' and analysis['virality_potential'] > 70:
prime_times = {
'Morning Rush': (8, 10),
'Lunch Break': (12, 14),
'Evening Prime': (18, 21)
}
else:
prime_times = {
'Mid-Morning': (10, 12),
'Afternoon': (14, 16),
'Late Evening': (20, 22)
}
# Find next available prime time
for time_slot, (start, end) in prime_times.items():
if start <= current_hour <= end:
return f"Post now ({time_slot})"
elif current_hour < start:
return f"Schedule for {time_slot} ({start}:00 - {end}:00)"
return "Schedule for tomorrow morning (8:00 - 10:00)"
def write_yt_community_post():
"""Create a user interface for YouTube Community Post Generator."""
st.write("Generate engaging community posts that drive interaction and channel growth.")
# Initialize session state
if "generated_post" not in st.session_state:
st.session_state.generated_post = None
if "post_history" not in st.session_state:
st.session_state.post_history = []
# Create tabs for different sections
tab1, tab2, tab3 = st.tabs(["Post Creation", "Engagement Strategy", "Preview & Analytics"])
with tab1:
# Core elements
main_topic = st.text_area("Main Topic/Message",
placeholder="e.g., New video announcement, Channel update, Question for viewers")
col1, col2 = st.columns(2)
with col1:
post_type = st.selectbox("Post Type", [
"Question",
"Poll",
"Behind the Scenes",
"Sneak Peek",
"Channel Update",
"Milestone Celebration",
"Content Preview",
"Fan Spotlight",
"Quick Tip",
"Discussion Starter"
])
target_audience = st.text_input("Target Audience",
placeholder="e.g., Tech enthusiasts, Gamers, DIY lovers")
with col2:
content_purpose = st.selectbox("Content Purpose", [
"Build Hype",
"Drive Discussion",
"Gather Feedback",
"Share Updates",
"Boost Engagement"
])
tone_style = st.selectbox("Tone/Style", [
"Casual",
"Professional",
"Excited",
"Mysterious",
"Humorous",
"Informative"
])
channel_niche = st.text_input("Channel Niche",
placeholder="e.g., Tech Reviews, Gaming, Education")
with tab2:
# Engagement options
st.subheader("Engagement Elements")
col1, col2 = st.columns(2)
with col1:
include_emoji = st.checkbox("Include Emojis", value=True)
include_hashtags = st.checkbox("Include Hashtags", value=True)
max_length = st.number_input("Maximum Length (characters)",
min_value=100, max_value=2000, value=500)
with col2:
include_poll = st.checkbox("Include Poll", value=False)
include_image_prompt = st.checkbox("Include Image Suggestions", value=True)
include_timing_suggestion = st.checkbox("Include Timing Suggestion", value=True)
# Advanced options
st.subheader("Advanced Options")
language = st.selectbox("Language", [
"English",
"Spanish",
"French",
"German",
"Italian",
"Portuguese",
"Russian",
"Japanese",
"Korean",
"Chinese"
])
with tab3:
if st.session_state.generated_post:
# Display the generated post
st.subheader("Generated Community Post")
# Create tabs for different views
post_tab1, post_tab2, post_tab3 = st.tabs(["Preview", "Analytics", "History"])
with post_tab1:
st.markdown(st.session_state.generated_post)
# Quick actions
col1, col2 = st.columns(2)
with col1:
if st.button("Copy to Clipboard"):
st.code(st.session_state.generated_post)
st.success("Post copied to clipboard!")
with col2:
if st.button("Save to History"):
st.session_state.post_history.append({
'post': st.session_state.generated_post,
'timestamp': datetime.now(),
'type': post_type
})
st.success("Post saved to history!")
with post_tab2:
# Analyze the post
analysis = analyze_post_engagement(st.session_state.generated_post)
# Create expandable sections for different analysis categories
with st.expander("📊 Overall Performance Metrics", expanded=True):
cols = st.columns(3)
with cols[0]:
score = analysis['engagement_score']
color = "red" if score < 60 else "orange" if score < 80 else "green"
st.markdown(f"### Overall Score: <span style='color: {color}'>{score:.1f}%</span>",
unsafe_allow_html=True)
# Sentiment Analysis
st.markdown("#### Sentiment Analysis")
st.metric("Polarity", f"{analysis['sentiment_analysis']['polarity']}%")
st.metric("Subjectivity", f"{analysis['sentiment_analysis']['subjectivity']}%")
st.info(f"Tone: {analysis['sentiment_analysis']['tone']}")
with cols[1]:
st.markdown("#### Engagement Metrics")
st.metric("Emotional Impact", f"{analysis['emotional_triggers']}%")
st.metric("CTA Strength", f"{analysis['call_to_action_strength']}%")
st.metric("Virality Potential", f"{analysis['virality_potential']:.1f}%")
with cols[2]:
st.markdown("#### Content Quality")
st.metric("Audience Resonance", f"{analysis['audience_resonance']:.1f}%")
st.metric("SEO Score", f"{analysis['seo_optimization']:.1f}%")
if analysis['timing_recommendation']:
st.success(f"📅 {analysis['timing_recommendation']}")
with st.expander("🎯 Psychological Triggers & Patterns"):
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Active Psychological Triggers")
if analysis['psychological_triggers']:
for trigger in analysis['psychological_triggers']:
st.markdown(f"{trigger.title()}")
else:
st.info("No strong psychological triggers detected")
with col2:
st.markdown("#### Engagement Patterns")
patterns = analysis['engagement_patterns']
for pattern, score in patterns.items():
st.metric(pattern.replace('_', ' ').title(), f"{score}%")
with st.expander("📝 Content Structure Analysis"):
col1, col2 = st.columns(2)
with col1:
structure = analysis['content_structure']
st.markdown("#### Structure Metrics")
for metric, score in structure.items():
st.metric(
metric.replace('_', ' ').title(),
f"{score:.1f}%"
)
with col2:
st.markdown("#### SEO Analysis")
st.metric("Hashtag Quality", f"{analyze_hashtag_quality(st.session_state.generated_post)}%")
st.metric("Keyword Density", f"{analyze_keyword_density(st.session_state.generated_post)}%")
st.metric("Mention Optimization", f"{analyze_mentions(st.session_state.generated_post)}%")
# Show improvement suggestions
if analysis['improvement_suggestions']:
with st.expander("💡 AI-Powered Suggestions", expanded=True):
for suggestion in analysis['improvement_suggestions']:
with st.container():
st.markdown(f"#### {suggestion['category']}")
st.info(suggestion['suggestion'])
if suggestion['examples']:
st.markdown("**Examples:**")
for example in suggestion['examples']:
st.markdown(f"- {example}")
# Add a refresh button for analysis
if st.button("🔄 Refresh Analysis"):
st.rerun()
with post_tab3:
if st.session_state.post_history:
st.subheader("Previous Posts")
for i, post in enumerate(reversed(st.session_state.post_history)):
with st.expander(f"Post {len(st.session_state.post_history)-i}: "
f"{post['type']} - "
f"{post['timestamp'].strftime('%Y-%m-%d %H:%M')}"):
st.write(post['post'])
else:
st.info("No post history yet. Save posts to see them here!")
# Generate button
if st.button("Generate Community Post"):
if not main_topic:
st.error("Please enter a main topic/message.")
return
with st.spinner("Generating community post..."):
post = generate_community_post(
post_type, main_topic, target_audience, tone_style,
content_purpose, channel_niche, include_emoji,
include_hashtags, include_poll, include_image_prompt,
include_timing_suggestion, max_length, language
)
if post:
st.session_state.generated_post = post
st.success("✨ Post generated successfully! Check the 'Preview & Analytics' tab to view, analyze, and save your post.")
st.rerun()
else:
st.error("Failed to generate post. Please try again.")

View File

@@ -0,0 +1,404 @@
"""
YouTube Description Generator Module
This module provides functionality for generating YouTube video descriptions.
"""
import streamlit as st
import time
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
def calculate_keyword_density(text, keywords):
"""Calculate the density of keywords in the text."""
if not text or not keywords:
return 0
text = text.lower()
keywords = [k.lower() for k in keywords]
total_words = len(text.split())
keyword_count = sum(text.count(k) for k in keywords)
return (keyword_count / total_words) * 100 if total_words > 0 else 0
def calculate_seo_score(text, keywords):
"""Calculate the SEO score of the description."""
score = 0
# Text length (optimal: 250-300 words)
word_count = len(text.split())
if 250 <= word_count <= 300:
score += 3
elif 200 <= word_count <= 350:
score += 2
elif 150 <= word_count <= 400:
score += 1
# Keyword presence
text_lower = text.lower()
keywords_lower = [k.lower() for k in keywords]
keyword_count = sum(text_lower.count(k) for k in keywords_lower)
if keyword_count >= 3:
score += 3
elif keyword_count >= 2:
score += 2
elif keyword_count >= 1:
score += 1
# Call to action phrases
cta_phrases = ["subscribe", "like", "comment", "share", "follow", "check out", "visit", "learn more"]
cta_count = sum(text_lower.count(phrase) for phrase in cta_phrases)
if cta_count >= 2:
score += 2
elif cta_count >= 1:
score += 1
# Hashtags
hashtag_count = text.count("#")
if 3 <= hashtag_count <= 5:
score += 2
elif 1 <= hashtag_count <= 8:
score += 1
# Links
link_count = text.count("http")
if 1 <= link_count <= 3:
score += 2
elif link_count > 3:
score += 1
return min(score, 10) # Cap at 10
def generate_youtube_description(target_audience, main_points, tone_style, use_case, primary_keywords,
secondary_keywords, language, seo_goals, include_timestamps=False,
include_hashtags=False, include_social_handles=False):
"""Generate a YouTube description based on the provided parameters."""
# Create a custom system prompt for YouTube description generation
system_prompt = """You are a YouTube description expert specializing in creating engaging, SEO-optimized video descriptions.
Your task is to generate YouTube video descriptions based on the provided information.
Focus ONLY on creating descriptions that are optimized for YouTube, with proper formatting, keywords, and calls to action.
Return ONLY the description text, without any additional commentary or explanations."""
# Build the prompt
prompt = f"""
**Instructions:**
Please generate a YouTube description for a video about **{main_points}** based on the following information:
**Target Audience:** {target_audience}
**Tone and Style:** {tone_style}
**Use Case:** {use_case}
**Language:** {language}
**Primary Keywords:** {primary_keywords}
**Secondary Keywords:** {secondary_keywords}
**SEO Goals:** {seo_goals}
**Additional Elements:**
{"- Include timestamps for key sections." if include_timestamps else ""}
{"- Include relevant hashtags." if include_hashtags else ""}
{"- Include social media handles." if include_social_handles else ""}
**Specific Instructions:**
* Keep the description informative and engaging.
* Use a conversational tone that matches the target audience.
* Include relevant keywords naturally.
* Add a call to action.
* Keep the length between 250-300 words for optimal SEO.
"""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response
except Exception as err:
st.error(f"Error: Failed to get response from LLM: {err}")
return None
def write_yt_description():
"""Create a user interface for YouTube Description Generator."""
st.write("Generate SEO-optimized YouTube video descriptions that drive engagement.")
# Initialize session state for generated description if it doesn't exist
if "generated_description" not in st.session_state:
st.session_state.generated_description = None
# Create tabs for different sections
tab1, tab2, tab3 = st.tabs(["Basic Info", "SEO Optimization", "Advanced Options"])
with tab1:
# Basic information inputs
main_points = st.text_area("Main Points/Keywords (comma-separated)",
placeholder="e.g., cooking tips, healthy recipes, quick meals")
# Create columns for the other inputs
col1, col2, col3, col4 = st.columns(4)
with col1:
tone_style = st.selectbox("Tone/Style",
["Professional", "Casual", "Humorous", "Educational", "Entertaining", "Inspirational"])
with col2:
target_audience = st.text_input("Target Audience",
placeholder="e.g., beginners, professionals, parents")
with col3:
use_case = st.selectbox("Use Case",
["How-to/Tutorial", "Vlog", "Review", "Educational", "Entertainment", "News"])
with col4:
language = st.selectbox("Language", ["English", "Spanish", "French", "German", "Italian", "Portuguese"])
with tab2:
# SEO optimization inputs
primary_keywords = st.text_input("Primary Keywords (comma-separated)",
placeholder="e.g., cooking, recipes, healthy food")
secondary_keywords = st.text_input("Secondary Keywords (comma-separated)",
placeholder="e.g., quick meals, budget cooking")
seo_goals = st.multiselect("SEO Goals",
["Increase Views", "Drive Engagement", "Build Subscribers", "Promote Products/Services"])
with tab3:
# Advanced options
st.subheader("Additional Elements")
include_timestamps = st.checkbox("Include Timestamps", value=True)
include_hashtags = st.checkbox("Include Hashtags", value=True)
include_social_handles = st.checkbox("Include Social Media Handles", value=True)
if st.button("Generate Description"):
if not main_points:
st.error("Please enter main points/keywords.")
return
with st.spinner("Generating description..."):
description = generate_youtube_description(
target_audience, main_points, tone_style, use_case, primary_keywords,
secondary_keywords, language, seo_goals, include_timestamps,
include_hashtags, include_social_handles
)
if description:
# Store the description in session state
st.session_state.generated_description = description
# Store other parameters in session state for regeneration
st.session_state.description_params = {
"target_audience": target_audience,
"main_points": main_points,
"tone_style": tone_style,
"use_case": use_case,
"primary_keywords": primary_keywords,
"secondary_keywords": secondary_keywords,
"language": language,
"seo_goals": seo_goals,
"include_timestamps": include_timestamps,
"include_hashtags": include_hashtags,
"include_social_handles": include_social_handles
}
st.subheader("Generated Description")
# Display description with analysis
st.text_area("Description", description, height=200)
# Calculate and display metrics
all_keywords = primary_keywords.split(",") + secondary_keywords.split(",")
keyword_density = calculate_keyword_density(description, all_keywords)
seo_score = calculate_seo_score(description, all_keywords)
col1, col2 = st.columns(2)
with col1:
st.metric("Keyword Density", f"{keyword_density:.1f}%")
with col2:
st.metric("SEO Score", f"{seo_score}/10")
# Create columns for the buttons
btn_col1, btn_col2 = st.columns(2)
with btn_col1:
# Download button
st.download_button(
label="Download Description",
data=description,
file_name="youtube_description.txt",
mime="text/plain"
)
with btn_col2:
# Regenerate button
if st.button("Regenerate"):
st.session_state.show_regenerate_popover = True
# Regenerate popover
if st.session_state.get("show_regenerate_popover", False):
with st.form("regenerate_form"):
st.subheader("Regenerate Description")
st.write("Specify changes you'd like to make to the description:")
changes = st.text_area("Changes to make",
placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits")
submitted = st.form_submit_button("Regenerate with Changes")
if submitted and changes:
with st.spinner("Regenerating description..."):
# Get the stored parameters
params = st.session_state.description_params
# Add the changes to the prompt
params["changes"] = changes
# Generate a new description with the changes
new_description = generate_youtube_description_with_changes(
params["target_audience"],
params["main_points"],
params["tone_style"],
params["use_case"],
params["primary_keywords"],
params["secondary_keywords"],
params["language"],
params["seo_goals"],
params["include_timestamps"],
params["include_hashtags"],
params["include_social_handles"],
changes
)
if new_description:
# Update the stored description
st.session_state.generated_description = new_description
st.session_state.show_regenerate_popover = False
st.rerun()
else:
st.error("Failed to regenerate description. Please try again.")
else:
st.error("Failed to generate description. Please try again.")
# Display previously generated description if it exists in session state
elif st.session_state.generated_description:
description = st.session_state.generated_description
params = st.session_state.description_params
st.subheader("Generated Description")
# Display description with analysis
st.text_area("Description", description, height=200)
# Calculate and display metrics
all_keywords = params["primary_keywords"].split(",") + params["secondary_keywords"].split(",")
keyword_density = calculate_keyword_density(description, all_keywords)
seo_score = calculate_seo_score(description, all_keywords)
col1, col2 = st.columns(2)
with col1:
st.metric("Keyword Density", f"{keyword_density:.1f}%")
with col2:
st.metric("SEO Score", f"{seo_score}/10")
# Create columns for the buttons
btn_col1, btn_col2 = st.columns(2)
with btn_col1:
# Download button
st.download_button(
label="Download Description",
data=description,
file_name="youtube_description.txt",
mime="text/plain"
)
with btn_col2:
# Regenerate button
if st.button("Regenerate"):
st.session_state.show_regenerate_popover = True
# Regenerate popover
if st.session_state.get("show_regenerate_popover", False):
with st.form("regenerate_form"):
st.subheader("Regenerate Description")
st.write("Specify changes you'd like to make to the description:")
changes = st.text_area("Changes to make",
placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits")
submitted = st.form_submit_button("Regenerate with Changes")
if submitted and changes:
with st.spinner("Regenerating description..."):
# Add the changes to the prompt
params["changes"] = changes
# Generate a new description with the changes
new_description = generate_youtube_description_with_changes(
params["target_audience"],
params["main_points"],
params["tone_style"],
params["use_case"],
params["primary_keywords"],
params["secondary_keywords"],
params["language"],
params["seo_goals"],
params["include_timestamps"],
params["include_hashtags"],
params["include_social_handles"],
changes
)
if new_description:
# Update the stored description
st.session_state.generated_description = new_description
st.session_state.show_regenerate_popover = False
st.rerun()
else:
st.error("Failed to regenerate description. Please try again.")
def generate_youtube_description_with_changes(target_audience, main_points, tone_style, use_case, primary_keywords,
secondary_keywords, language, seo_goals, include_timestamps=False,
include_hashtags=False, include_social_handles=False, changes=""):
"""Generate a YouTube description based on the provided parameters and requested changes."""
# Create a custom system prompt for YouTube description generation
system_prompt = """You are a YouTube description expert specializing in creating engaging, SEO-optimized video descriptions.
Your task is to generate YouTube video descriptions based on the provided information.
Focus ONLY on creating descriptions that are optimized for YouTube, with proper formatting, keywords, and calls to action.
Return ONLY the description text, without any additional commentary or explanations."""
# Build the prompt
prompt = f"""
**Instructions:**
Please generate a YouTube description for a video about **{main_points}** based on the following information:
**Target Audience:** {target_audience}
**Tone and Style:** {tone_style}
**Use Case:** {use_case}
**Language:** {language}
**Primary Keywords:** {primary_keywords}
**Secondary Keywords:** {secondary_keywords}
**SEO Goals:** {seo_goals}
**Additional Elements:**
{"- Include timestamps for key sections." if include_timestamps else ""}
{"- Include relevant hashtags." if include_hashtags else ""}
{"- Include social media handles." if include_social_handles else ""}
**Requested Changes:**
{changes}
**Specific Instructions:**
* Keep the description informative and engaging.
* Use a conversational tone that matches the target audience.
* Include relevant keywords naturally.
* Add a call to action.
* Keep the length between 250-300 words for optimal SEO.
* Incorporate the requested changes into the description.
"""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response
except Exception as err:
st.error(f"Error: Failed to get response from LLM: {err}")
return None

View File

@@ -0,0 +1,740 @@
"""
YouTube End Screen Generator Module
This module provides functionality for generating YouTube video end screens.
"""
import streamlit as st
import time
import logging
import traceback
from PIL import Image
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.gpt_providers.text_to_image_generation.gen_gemini_images import generate_gemini_image, edit_image
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('youtube_end_screen_generator')
def generate_end_screen_concepts(video_title, video_description, target_audience, content_type,
primary_goal, secondary_goal=None, num_concepts=3):
"""Generate end screen concept ideas based on video content."""
logger.info(f"Generating end screen concepts for: '{video_title}'")
logger.info(f"Parameters: target_audience={target_audience}, content_type={content_type}, "
f"primary_goal={primary_goal}, secondary_goal={secondary_goal}, num_concepts={num_concepts}")
# Create a system prompt for end screen concept generation
system_prompt = """You are a YouTube end screen expert specializing in creating engaging, action-driving end screen concepts.
Your task is to generate end screen concept ideas based on the provided video information.
Focus ONLY on creating end screens that are optimized for YouTube, with proper visual hierarchy, element placement, and call-to-action triggers.
Return ONLY the concept descriptions, without any additional commentary or explanations.
Each concept should include:
1. A main visual element or background
2. Element placement and content (subscribe button, playlist, video, website)
3. Color scheme suggestions
4. Text content for each element
5. Brief explanation of why this concept would be effective for the specified goals
IMPORTANT: Format each concept with a clear numbered heading like "1. [Concept Name]" to ensure proper parsing."""
# Build the prompt
prompt = f"""
**Instructions:**
Please generate {num_concepts} end screen concept ideas for a YouTube video with the following information:
**Video Title:** {video_title}
**Video Description:** {video_description}
**Target Audience:** {target_audience}
**Content Type:** {content_type}
**Primary Goal:** {primary_goal}
**Secondary Goal:** {secondary_goal if secondary_goal else "None specified"}
**Specific Instructions:**
* Each concept should be clearly separated and numbered with a heading like "1. [Concept Name]".
* Focus on creating end screens that drive the specified goals.
* Consider the target audience's interests and preferences.
* Include specific details about visual elements, element placement, and color schemes.
* Explain why each concept would be effective for this specific video and goals.
* Include text suggestions for each element (subscribe button, playlist, video, website).
"""
try:
logger.info("Sending request to LLM for end screen concepts")
response = llm_text_gen(prompt, system_prompt=system_prompt)
logger.info(f"Received response from LLM: {len(response)} characters")
return response
except Exception as err:
logger.error(f"Error generating end screen concepts: {err}")
logger.error(traceback.format_exc())
st.error(f"Error: Failed to generate end screen concepts: {err}")
return None
def generate_end_screen_design(concept_description, style_preference, element_count=2,
element_types=None, element_texts=None, aspect_ratio="16:9",
keywords=None, style=None, focus=None):
"""Generate an end screen image based on the concept description."""
logger.info(f"Generating end screen design for concept: '{concept_description[:50]}...'")
logger.info(f"Parameters: style_preference={style_preference}, element_count={element_count}, "
f"element_types={element_types}, element_texts={element_texts}, aspect_ratio={aspect_ratio}")
# Extract key elements from the concept description
# This helps focus the prompt on the most important aspects
concept_lines = concept_description.split('\n')
main_visual = ""
element_placement = ""
color_scheme = ""
text_content = ""
for line in concept_lines:
if "visual" in line.lower() or "background" in line.lower():
main_visual = line
elif "placement" in line.lower() or "layout" in line.lower():
element_placement = line
elif "color" in line.lower() or "scheme" in line.lower():
color_scheme = line
elif "text" in line.lower() or "content" in line.lower():
text_content = line
# Create a more focused prompt for the image generation
image_prompt = f"""
Create a YouTube end screen image with the following specifications:
MAIN VISUAL: {main_visual if main_visual else "Not specified"}
ELEMENT PLACEMENT: {element_placement if element_placement else "Not specified"}
COLOR SCHEME: {color_scheme if color_scheme else "Not specified"}
TEXT CONTENT: {text_content if text_content else "Not specified"}
STYLE: {style_preference}
ASPECT RATIO: {aspect_ratio}
NUMBER OF ELEMENTS: {element_count}
ELEMENT TYPES: {', '.join(element_types) if element_types else 'Not specified'}
ELEMENT TEXTS: {', '.join(element_texts) if element_texts else 'Not specified'}
IMPORTANT REQUIREMENTS:
1. This must be a VISUAL IMAGE of a YouTube end screen, not just a text description
2. The image should be high contrast and visually striking
3. All text should be large and readable
4. Elements should be properly placed for optimal viewer engagement
5. The design should follow the specified color scheme
6. The image should be optimized for the specified aspect ratio
PLEASE GENERATE AN ACTUAL IMAGE, NOT JUST A TEXT DESCRIPTION.
"""
try:
logger.info("Sending request to Gemini for end screen image")
# Generate the image using Gemini with enhanced prompt
img_path = generate_gemini_image(
image_prompt,
keywords=keywords,
style=style,
focus=focus,
enhance_prompt=True
)
logger.info(f"Received image from Gemini: {img_path}")
return img_path
except Exception as err:
logger.error(f"Error generating end screen image: {err}")
logger.error(traceback.format_exc())
st.error(f"Error: Failed to generate end screen image: {err}")
return None
def edit_end_screen_image(img_path, edit_instructions):
"""Edit an end screen image based on user instructions."""
logger.info(f"Editing end screen image: '{img_path}'")
logger.info(f"Edit instructions: '{edit_instructions}'")
try:
logger.info("Sending request to Gemini for image editing")
# Edit the image using Gemini
edited_img_path = edit_image(img_path, f"Edit this image according to these instructions: {edit_instructions}. IMPORTANT: Please generate an actual edited image, not just a text description. I need a visual representation of the edited end screen.")
logger.info(f"Image editing completed. Edited image path: {edited_img_path}")
# Return the path to the edited image
return edited_img_path
except Exception as err:
logger.error(f"Error editing end screen image: {err}")
logger.error(traceback.format_exc())
st.error(f"Error: Failed to edit end screen image: {err}")
return None
def analyze_end_screen(end_screen_path):
"""Analyze an end screen for effectiveness."""
logger.info(f"Analyzing end screen: '{end_screen_path}'")
# This would typically involve image analysis, but for now we'll use AI to provide feedback
system_prompt = """You are a YouTube end screen expert specializing in analyzing and providing feedback on end screen designs.
Your task is to analyze the end screen and provide constructive feedback on its effectiveness.
Focus on aspects like visual hierarchy, element placement, call-to-action clarity, and overall effectiveness."""
# For now, we'll just return a placeholder analysis
# In a real implementation, we would analyze the actual image
logger.info("Generating end screen analysis")
return """
**End Screen Analysis:**
- **Visual Hierarchy:** The main elements are well-positioned and stand out against the background.
- **Element Placement:** The call-to-action elements are strategically placed for optimal viewer engagement.
- **Call-to-Action Clarity:** The text and visual cues clearly communicate the desired actions.
- **Overall Effectiveness:** The design is likely to drive the specified goals due to its visual appeal and clear value proposition.
**Suggestions for Improvement:**
- Consider adding a subtle animation hint to draw attention to the most important element.
- The text could be slightly larger for better readability on mobile devices.
- Adding a small icon or logo could help with brand recognition.
"""
def parse_concepts(concepts_text):
"""Parse the concepts text into a list of individual concepts."""
logger.info("Parsing concepts text into individual concepts")
# Split the concepts text by main concept headers
concepts = []
current_concept = ""
# Look for patterns like numbered headings (e.g., "1.", "2.", "3.") or "Concept 1:", "Concept 2:", etc.
concept_patterns = ["1.", "2.", "3.", "4.", "5.", "Concept 1:", "Concept 2:", "Concept 3:", "Concept 4:", "Concept 5:"]
for line in concepts_text.split('\n'):
# Check if line starts with a concept pattern
is_new_concept = False
for pattern in concept_patterns:
if line.strip().startswith(pattern):
# If we have a previous concept, add it to the list
if current_concept:
concepts.append(current_concept.strip())
# Start a new concept
current_concept = line
is_new_concept = True
break
if not is_new_concept:
# Add the line to the current concept
current_concept += "\n" + line
# Add the last concept
if current_concept:
concepts.append(current_concept.strip())
logger.info(f"Parsed {len(concepts)} concepts from the response")
return concepts
def write_yt_end_screen():
"""Create a user interface for YouTube End Screen Generator."""
logger.info("Initializing YouTube End Screen Generator UI")
st.title("YouTube End Screen Generator")
st.write("Create engaging, action-driving end screens for your YouTube videos.")
# Initialize session state for generated end screens if it doesn't exist
if "generated_end_screens" not in st.session_state:
st.session_state.generated_end_screens = []
if "end_screen_concepts" not in st.session_state:
st.session_state.end_screen_concepts = None
if "current_end_screen_path" not in st.session_state:
st.session_state.current_end_screen_path = None
if "concept_list" not in st.session_state:
st.session_state.concept_list = []
if "editing_end_screen" not in st.session_state:
st.session_state.editing_end_screen = False
if "edit_instructions" not in st.session_state:
st.session_state.edit_instructions = ""
if "edited_end_screen_path" not in st.session_state:
st.session_state.edited_end_screen_path = None
if "show_edit_form" not in st.session_state:
st.session_state.show_edit_form = False
# Create tabs for different sections
tab1, tab2 = st.tabs(["Basic Info", "Style & Elements"])
with tab1:
# Basic information inputs
video_title = st.text_input("Video Title",
placeholder="e.g., 10 Tips for Better Photography")
video_description = st.text_area("Video Description",
placeholder="Brief description of your video content")
target_audience = st.text_input("Target Audience",
placeholder="e.g., photography enthusiasts, beginners")
# Content type selection
content_type = st.selectbox("Content Type", [
"Tutorial/How-to",
"Vlog",
"Review",
"Educational",
"Entertainment",
"News/Update",
"Product Showcase",
"Challenge",
"Reaction",
"Comparison"
])
# End screen goals
st.subheader("End Screen Goals")
primary_goal = st.selectbox("Primary Goal", [
"Drive Subscriptions",
"Promote Playlist",
"Promote Next Video",
"Promote Website",
"Promote Social Media",
"Promote Product/Service",
"Encourage Comments",
"Mixed Goals"
])
secondary_goal = st.selectbox("Secondary Goal (Optional)", [
"None",
"Drive Subscriptions",
"Promote Playlist",
"Promote Next Video",
"Promote Website",
"Promote Social Media",
"Promote Product/Service",
"Encourage Comments"
])
if secondary_goal == "None":
secondary_goal = None
with tab2:
# Style preferences
st.subheader("Style Preferences")
# Create columns for style options
col1, col2 = st.columns(2)
with col1:
style_preference = st.selectbox("End Screen Style", [
"Bold and Dramatic",
"Clean and Minimal",
"Colorful and Vibrant",
"Dark and Moody",
"Professional and Corporate",
"Playful and Fun",
"Retro/Vintage",
"Modern and Sleek"
])
num_concepts = st.slider("Number of Concepts", 1, 5, 3)
with col2:
aspect_ratio = st.selectbox("Aspect Ratio", [
"16:9 (Standard)",
"1:1 (Square)",
"4:3 (Classic)",
"9:16 (Vertical)"
])
include_branding = st.checkbox("Include Branding Elements", value=True)
if include_branding:
branding_elements = st.multiselect("Branding Elements", [
"Channel Logo",
"Channel Name",
"Channel Tagline",
"Brand Colors",
"Watermark"
])
# Element configuration
st.subheader("End Screen Elements")
# Number of elements
element_count = st.slider("Number of Elements", 1, 4, 2)
# Element types
element_types = []
element_texts = []
for i in range(element_count):
st.write(f"Element {i+1}")
col1, col2 = st.columns(2)
with col1:
element_type = st.selectbox(
f"Type",
["Subscribe Button", "Playlist", "Video", "Website", "Social Media"],
key=f"element_type_{i}"
)
element_types.append(element_type)
with col2:
element_text = st.text_input(
f"Text",
placeholder=f"Text for {element_type}",
key=f"element_text_{i}"
)
element_texts.append(element_text)
# Advanced AI Prompt Settings
st.subheader("Advanced AI Prompt Settings")
# Create columns for advanced settings
col3, col4 = st.columns(2)
with col3:
# Image style selection
image_style = st.selectbox("Image Style", [
"Auto (AI will choose best style)",
"Photorealistic",
"Artistic",
"Cartoon/Anime",
"Sketch/Drawing",
"Digital Art",
"3D Render"
])
# Extract style for the generate_gemini_image function
style = None
if image_style == "Photorealistic":
style = "photorealistic"
elif image_style == "Artistic":
style = "artistic"
elif image_style == "Cartoon/Anime":
style = "cartoon"
elif image_style == "Sketch/Drawing":
style = "sketch"
elif image_style == "Digital Art":
style = "digital_art"
elif image_style == "3D Render":
style = "3d_render"
with col4:
# Focus selection for photorealistic images
focus = None
if style == "photorealistic":
focus = st.selectbox("Image Focus", [
"Auto (AI will choose best focus)",
"Portraits",
"Objects",
"Motion",
"Wide-angle"
])
# Extract focus for the generate_gemini_image function
if focus == "Portraits":
focus = "portraits"
elif focus == "Objects":
focus = "objects"
elif focus == "Motion":
focus = "motion"
elif focus == "Wide-angle":
focus = "wide-angle"
elif focus == "Auto (AI will choose best focus)":
focus = None
# Keywords for enhanced prompt generation
st.subheader("Keywords for Enhanced Prompt")
st.write("Add keywords to enhance the AI prompt generation. These will help create more detailed and accurate end screens.")
# Create a text area for keywords
keywords_input = st.text_area(
"Keywords (comma-separated)",
placeholder="e.g., vibrant, energetic, bold, eye-catching, professional"
)
# Process keywords
keywords = None
if keywords_input:
keywords = [k.strip() for k in keywords_input.split(",") if k.strip()]
logger.info(f"User provided keywords: {keywords}")
# Generate button - placed outside of tabs for better visibility
st.markdown("---")
st.subheader("Generate End Screen Concepts")
st.write("Click the button below to generate end screen concepts based on your inputs.")
if st.button("Generate End Screen Concepts", type="primary"):
if not video_title:
st.error("Please enter a video title.")
return
with st.spinner("Generating end screen concepts..."):
logger.info("User clicked Generate End Screen Concepts button")
concepts = generate_end_screen_concepts(
video_title,
video_description,
target_audience,
content_type,
primary_goal,
secondary_goal,
num_concepts
)
if concepts:
# Store the concepts in session state
st.session_state.end_screen_concepts = concepts
# Parse the concepts and store in session state
st.session_state.concept_list = parse_concepts(concepts)
logger.info("Stored end screen concepts in session state")
# Display the concepts in tabs
st.subheader("End Screen Concepts")
# Create tabs for each concept
concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))])
for i, tab in enumerate(concept_tabs):
with tab:
st.markdown(st.session_state.concept_list[i])
# Add a button to generate image for this concept
if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_{i}"):
with st.spinner(f"Generating end screen image for concept {i+1}..."):
logger.info(f"User selected concept {i+1} for image generation")
# Get the selected concept
selected_concept = st.session_state.concept_list[i]
# Generate the end screen image with enhanced prompt
img_path = generate_end_screen_design(
selected_concept,
style_preference,
element_count,
element_types,
element_texts,
aspect_ratio.split()[0], # Extract just the ratio part
keywords=keywords,
style=style,
focus=focus
)
if img_path:
# Store the current end screen path in session state
st.session_state.current_end_screen_path = img_path
logger.info(f"Stored current end screen path in session state: {img_path}")
# Display the generated image
st.subheader("Generated End Screen")
st.image(img_path, use_container_width=True)
# Add download button
with open(img_path, "rb") as file:
st.download_button(
label="Download End Screen",
data=file,
file_name=f"youtube_end_screen_{int(time.time())}.png",
mime="image/png"
)
# Add image editing section
st.subheader("Edit End Screen")
st.write("Make changes to your end screen by providing instructions below:")
# Create a text area for edit instructions
edit_instructions = st.text_area(
"Edit Instructions",
placeholder="e.g., Make the background darker, Add a red border, Change the text color to white",
key=f"edit_instructions_{i}"
)
# Store edit instructions in session state
st.session_state.edit_instructions = edit_instructions
# Add a button to apply edits
if st.button("Apply Edits", key=f"apply_edits_{i}"):
if not edit_instructions:
st.warning("Please provide edit instructions.")
else:
# Set editing flag
st.session_state.editing_end_screen = True
st.session_state.show_edit_form = True
# Rerun to update the UI
st.rerun()
# Add analysis button
if st.button("Analyze End Screen", key=f"analyze_{i}"):
logger.info("User clicked Analyze End Screen button")
analysis = analyze_end_screen(img_path)
st.subheader("End Screen Analysis")
st.markdown(analysis)
else:
st.error("Failed to generate end screen concepts. Please try again.")
# Display previously generated concepts if they exist in session state
elif st.session_state.end_screen_concepts and st.session_state.concept_list:
logger.info("Displaying previously generated concepts from session state")
st.subheader("End Screen Concepts")
# Create tabs for each concept
concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))])
for i, tab in enumerate(concept_tabs):
with tab:
st.markdown(st.session_state.concept_list[i])
# Add a button to generate image for this concept
if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_existing_{i}"):
with st.spinner(f"Generating end screen image for concept {i+1}..."):
logger.info(f"User selected concept {i+1} for image generation")
# Get the selected concept
selected_concept = st.session_state.concept_list[i]
# Generate the end screen image with enhanced prompt
img_path = generate_end_screen_design(
selected_concept,
style_preference,
element_count,
element_types,
element_texts,
aspect_ratio.split()[0], # Extract just the ratio part
keywords=keywords,
style=style,
focus=focus
)
if img_path:
# Store the current end screen path in session state
st.session_state.current_end_screen_path = img_path
logger.info(f"Stored current end screen path in session state: {img_path}")
# Display the generated image
st.subheader("Generated End Screen")
st.image(img_path, use_container_width=True)
# Add download button
with open(img_path, "rb") as file:
st.download_button(
label="Download End Screen",
data=file,
file_name=f"youtube_end_screen_{int(time.time())}.png",
mime="image/png"
)
# Add image editing section
st.subheader("Edit End Screen")
st.write("Make changes to your end screen by providing instructions below:")
# Create a text area for edit instructions
edit_instructions = st.text_area(
"Edit Instructions",
placeholder="e.g., Make the background darker, Add a red border, Change the text color to white",
key=f"edit_instructions_existing_{i}"
)
# Store edit instructions in session state
st.session_state.edit_instructions = edit_instructions
# Add a button to apply edits
if st.button("Apply Edits", key=f"apply_edits_existing_{i}"):
if not edit_instructions:
st.warning("Please provide edit instructions.")
else:
# Set editing flag
st.session_state.editing_end_screen = True
st.session_state.show_edit_form = True
# Rerun to update the UI
st.rerun()
# Add analysis button
if st.button("Analyze End Screen", key=f"analyze_existing_{i}"):
logger.info("User clicked Analyze End Screen button")
analysis = analyze_end_screen(img_path)
st.subheader("End Screen Analysis")
st.markdown(analysis)
# Display current end screen if it exists in session state
elif st.session_state.current_end_screen_path:
logger.info(f"Displaying current end screen from session state: {st.session_state.current_end_screen_path}")
st.subheader("Current End Screen")
st.image(st.session_state.current_end_screen_path, use_container_width=True)
# Add download button
with open(st.session_state.current_end_screen_path, "rb") as file:
st.download_button(
label="Download End Screen",
data=file,
file_name=f"youtube_end_screen_{int(time.time())}.png",
mime="image/png"
)
# Add image editing section
st.subheader("Edit End Screen")
st.write("Make changes to your end screen by providing instructions below:")
# Create a text area for edit instructions
edit_instructions = st.text_area(
"Edit Instructions",
placeholder="e.g., Make the background darker, Add a new element, Change the text color to white",
key="edit_instructions_current",
value=st.session_state.edit_instructions if st.session_state.edit_instructions else ""
)
# Store edit instructions in session state
st.session_state.edit_instructions = edit_instructions
# Add a button to apply edits
if st.button("Apply Edits", key="apply_edits_current"):
if not edit_instructions:
st.warning("Please provide edit instructions.")
else:
# Set editing flag
st.session_state.editing_end_screen = True
st.session_state.show_edit_form = True
# Rerun to update the UI
st.rerun()
# Add analysis button
if st.button("Analyze End Screen", key="analyze_current"):
logger.info("User clicked Analyze End Screen button")
analysis = analyze_end_screen(st.session_state.current_end_screen_path)
st.subheader("End Screen Analysis")
st.markdown(analysis)
# Handle the editing process
if st.session_state.editing_end_screen and st.session_state.show_edit_form:
st.subheader("Editing End Screen")
# Show a spinner while editing
with st.spinner("Editing end screen..."):
logger.info(f"User provided edit instructions: '{st.session_state.edit_instructions}'")
# Edit the end screen image
edited_img_path = edit_end_screen_image(st.session_state.current_end_screen_path, st.session_state.edit_instructions)
if edited_img_path:
# Update the current end screen path in session state
st.session_state.edited_end_screen_path = edited_img_path
logger.info(f"Updated current end screen path in session state: {edited_img_path}")
# Reset editing flags
st.session_state.editing_end_screen = False
st.session_state.show_edit_form = False
# Display the edited image
st.subheader("Edited End Screen")
st.image(edited_img_path, use_container_width=True)
# Add download button for the edited image
with open(edited_img_path, "rb") as file:
st.download_button(
label="Download Edited End Screen",
data=file,
file_name=f"youtube_end_screen_edited_{int(time.time())}.png",
mime="image/png"
)
# Update the current end screen path to the edited one
st.session_state.current_end_screen_path = edited_img_path
# Add a button to continue editing
if st.button("Continue Editing"):
st.session_state.show_edit_form = True
st.rerun()
else:
# Reset editing flags
st.session_state.editing_end_screen = False
st.session_state.show_edit_form = False
st.error("Failed to edit the end screen. Please try again with different instructions.")

View File

@@ -0,0 +1,556 @@
"""
YouTube Script Generator Module
This module provides functionality for generating YouTube video scripts.
"""
import streamlit as st
import time
import json
import os
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
def generate_youtube_script(target_audience, main_points, tone_style, use_case, script_structure,
include_hook=False, include_cta=False, include_engagement=False,
include_timestamps=False, include_visual_cues=False, engagement_hooks=None,
community_interactions=None, language="English"):
"""Generate a YouTube script based on the provided parameters."""
# Create a custom system prompt for YouTube script generation
system_prompt = f"""You are a YouTube script expert specializing in creating engaging, well-structured video scripts in {language}.
Your task is to generate YouTube video scripts based on the provided information.
Focus ONLY on creating scripts that are optimized for YouTube, with proper structure, engagement hooks, and calls to action.
Return ONLY the script text, without any additional commentary or explanations.
Format the script with clear sections, speaker notes, and visual cues where appropriate.
Write the entire script in {language}."""
# Build structure-specific instructions
structure_instructions = {
"Problem-Solution": "Structure the script to first present a problem, then provide a solution.",
"Before-After-Bridge": "Structure the script to show the before state, the transformation process, and the after state.",
"Hook-Problem-Solution-Call to Action": "Start with a hook, present the problem, provide the solution, and end with a call to action.",
"Compare and Contrast": "Structure the script to compare and contrast different options or approaches.",
"Step-by-Step Tutorial": "Break down the content into clear, sequential steps.",
"Case Study": "Present a real-world example or case study to illustrate the main points.",
"Interview Format": "Structure the script as an interview with questions and answers.",
"Review Format": "Structure the script as a review with pros, cons, and a final verdict.",
"Vlog Format": "Structure the script as a personal video blog with a conversational tone.",
"Educational Format": "Structure the script to teach a concept with examples and explanations.",
"Entertainment Format": "Structure the script to entertain while delivering the main message."
}
# Build the prompt
prompt = f"""
**Instructions:**
Please generate a YouTube script in {language} for a video about **{main_points}** based on the following information:
**Target Audience:** {target_audience}
**Tone and Style:** {tone_style}
**Use Case:** {use_case}
**Script Structure:** {script_structure}
**Language:** {language}
**Structure Instructions:**
{structure_instructions.get(script_structure, "Follow a logical flow to present the content.")}
**Additional Elements:**
{"- Include a hook at the beginning to grab attention." if include_hook else ""}
{"- End with a strong call to action." if include_cta else ""}
{"- Include prompts for viewer engagement (e.g., questions, polls)." if include_engagement else ""}
{"- Include suggested timestamps for key sections." if include_timestamps else ""}
{"- Include visual cues and transitions." if include_visual_cues else ""}
"""
# Add engagement hooks if provided
if engagement_hooks:
prompt += "\n**Engagement Hooks:**\n"
for hook in engagement_hooks:
prompt += f"- {hook}\n"
# Add community interaction points if provided
if community_interactions:
prompt += "\n**Community Interaction Points:**\n"
for interaction in community_interactions:
prompt += f"- {interaction}\n"
prompt += """
**Specific Instructions:**
* Keep the language clear and engaging.
* Use a conversational tone that matches the target audience.
* Include relevant examples and explanations.
* Ensure the script flows naturally and maintains viewer interest.
"""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response
except Exception as err:
st.error(f"Error: Failed to get response from LLM: {err}")
return None
def generate_youtube_script_with_changes(target_audience, main_points, tone_style, use_case, script_structure,
include_hook=False, include_cta=False, include_engagement=False,
include_timestamps=False, include_visual_cues=False, engagement_hooks=None,
community_interactions=None, changes="", language="English"):
"""Generate a YouTube script based on the provided parameters and requested changes."""
# Create a custom system prompt for YouTube script generation
system_prompt = f"""You are a YouTube script expert specializing in creating engaging, well-structured video scripts in {language}.
Your task is to generate YouTube video scripts based on the provided information.
Focus ONLY on creating scripts that are optimized for YouTube, with proper structure, engagement hooks, and calls to action.
Return ONLY the script text, without any additional commentary or explanations.
Format the script with clear sections, speaker notes, and visual cues where appropriate.
Write the entire script in {language}."""
# Build structure-specific instructions
structure_instructions = {
"Problem-Solution": "Structure the script to first present a problem, then provide a solution.",
"Before-After-Bridge": "Structure the script to show the before state, the transformation process, and the after state.",
"Hook-Problem-Solution-Call to Action": "Start with a hook, present the problem, provide the solution, and end with a call to action.",
"Compare and Contrast": "Structure the script to compare and contrast different options or approaches.",
"Step-by-Step Tutorial": "Break down the content into clear, sequential steps.",
"Case Study": "Present a real-world example or case study to illustrate the main points.",
"Interview Format": "Structure the script as an interview with questions and answers.",
"Review Format": "Structure the script as a review with pros, cons, and a final verdict.",
"Vlog Format": "Structure the script as a personal video blog with a conversational tone.",
"Educational Format": "Structure the script to teach a concept with examples and explanations.",
"Entertainment Format": "Structure the script to entertain while delivering the main message."
}
# Build the prompt
prompt = f"""
**Instructions:**
Please generate a YouTube script in {language} for a video about **{main_points}** based on the following information:
**Target Audience:** {target_audience}
**Tone and Style:** {tone_style}
**Use Case:** {use_case}
**Script Structure:** {script_structure}
**Language:** {language}
**Structure Instructions:**
{structure_instructions.get(script_structure, "Follow a logical flow to present the content.")}
**Additional Elements:**
{"- Include a hook at the beginning to grab attention." if include_hook else ""}
{"- End with a strong call to action." if include_cta else ""}
{"- Include prompts for viewer engagement (e.g., questions, polls)." if include_engagement else ""}
{"- Include suggested timestamps for key sections." if include_timestamps else ""}
{"- Include visual cues and transitions." if include_visual_cues else ""}
"""
# Add engagement hooks if provided
if engagement_hooks:
prompt += "\n**Engagement Hooks:**\n"
for hook in engagement_hooks:
prompt += f"- {hook}\n"
# Add community interaction points if provided
if community_interactions:
prompt += "\n**Community Interaction Points:**\n"
for interaction in community_interactions:
prompt += f"- {interaction}\n"
# Add requested changes
prompt += f"""
**Requested Changes:**
{changes}
**Specific Instructions:**
* Keep the language clear and engaging.
* Use a conversational tone that matches the target audience.
* Include relevant examples and explanations.
* Ensure the script flows naturally and maintains viewer interest.
* Incorporate the requested changes into the script.
"""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response
except Exception as err:
st.error(f"Error: Failed to get response from LLM: {err}")
return None
def export_script(script, format_type, filename=None):
"""Export the script in various formats."""
if not filename:
filename = "youtube_script"
if format_type == "Text":
return script, f"{filename}.txt", "text/plain"
elif format_type == "Markdown":
return script, f"{filename}.md", "text/markdown"
elif format_type == "HTML":
html_content = f"<html><body><pre>{script}</pre></body></html>"
return html_content, f"{filename}.html", "text/html"
elif format_type == "JSON":
json_content = json.dumps({"script": script}, indent=2)
return json_content, f"{filename}.json", "application/json"
elif format_type == "Subtitles (SRT)":
# Convert script to basic SRT format
lines = script.split('\n')
srt_content = ""
for i, line in enumerate(lines):
if line.strip():
start_time = f"00:00:{i*5:02d},000"
end_time = f"00:00:{(i+1)*5:02d},000"
srt_content += f"{i+1}\n{start_time} --> {end_time}\n{line}\n\n"
return srt_content, f"{filename}.srt", "text/plain"
else:
return script, f"{filename}.txt", "text/plain"
def write_yt_script():
"""Create a user interface for YouTube Script Generator."""
st.write("Generate professional YouTube video scripts with optimized structures for engagement.")
# Initialize session state for generated script if it doesn't exist
if "generated_script" not in st.session_state:
st.session_state.generated_script = None
# Create tabs for different sections
tab1, tab2, tab3 = st.tabs(["Basic Info", "Advanced Options", "Engagement & Export"])
with tab1:
# Basic information inputs
main_points = st.text_area("Main Points/Keywords (comma-separated)",
placeholder="e.g., cooking tips, healthy recipes, quick meals")
target_audience = st.text_input("Target Audience",
placeholder="e.g., beginners, professionals, parents")
# Create columns for tone, use case, structure, and language
col1, col2, col3, col4 = st.columns(4)
with col1:
tone_style = st.selectbox("Tone/Style",
["Professional", "Casual", "Humorous", "Educational", "Entertaining", "Inspirational"])
with col2:
use_case = st.selectbox("Use Case",
["How-to/Tutorial", "Vlog", "Review", "Educational", "Entertainment", "News"])
with col3:
script_structure = st.selectbox("Script Structure", [
"Problem-Solution",
"Before-After-Bridge",
"Hook-Problem-Solution-Call to Action",
"Compare and Contrast",
"Step-by-Step Tutorial",
"Case Study",
"Interview Format",
"Review Format",
"Vlog Format",
"Educational Format",
"Entertainment Format"
])
with col4:
language = st.selectbox("Language", [
"English",
"Spanish",
"French",
"German",
"Italian",
"Portuguese",
"Russian",
"Japanese",
"Korean",
"Chinese",
"Hindi",
"Arabic"
])
with tab2:
# Advanced options
st.subheader("Additional Elements")
include_hook = st.checkbox("Include Hook", value=True)
include_cta = st.checkbox("Include Call to Action", value=True)
include_engagement = st.checkbox("Include Viewer Engagement Prompts", value=True)
include_timestamps = st.checkbox("Include Suggested Timestamps", value=True)
include_visual_cues = st.checkbox("Include Visual Cues/Transitions", value=True)
with tab3:
# Engagement hooks
st.subheader("Engagement Hooks")
st.write("Select engagement hooks to include in your script:")
engagement_hooks = []
if st.checkbox("Question Hook", value=False):
engagement_hooks.append("Start with a thought-provoking question to engage viewers immediately")
if st.checkbox("Story Hook", value=False):
engagement_hooks.append("Begin with a brief, relevant story or anecdote")
if st.checkbox("Statistic Hook", value=False):
engagement_hooks.append("Open with an interesting statistic or fact")
if st.checkbox("Controversy Hook", value=False):
engagement_hooks.append("Present a controversial statement or opinion to spark interest")
if st.checkbox("Promise Hook", value=False):
engagement_hooks.append("Make a promise about what viewers will learn or gain")
if st.checkbox("Scenario Hook", value=False):
engagement_hooks.append("Describe a scenario or situation viewers can relate to")
if st.checkbox("Mystery Hook", value=False):
engagement_hooks.append("Create a sense of mystery or intrigue")
if st.checkbox("Quote Hook", value=False):
engagement_hooks.append("Start with a relevant quote from an expert or notable figure")
# Community interaction points
st.subheader("Community Interaction Points")
st.write("Select community interaction points to include in your script:")
community_interactions = []
if st.checkbox("Comment Prompt", value=False):
community_interactions.append("Ask viewers to share their experiences or opinions in the comments")
if st.checkbox("Poll Suggestion", value=False):
community_interactions.append("Suggest creating a poll for viewers to vote on")
if st.checkbox("Question for Comments", value=False):
community_interactions.append("Pose a specific question for viewers to answer in the comments")
if st.checkbox("Challenge", value=False):
community_interactions.append("Challenge viewers to try something and report back")
if st.checkbox("Tag Friends", value=False):
community_interactions.append("Encourage viewers to tag friends who might benefit from the content")
if st.checkbox("Share Request", value=False):
community_interactions.append("Ask viewers to share the video with others who might find it helpful")
if st.checkbox("Community Post", value=False):
community_interactions.append("Mention creating a community post to continue the discussion")
if st.checkbox("Live Stream Teaser", value=False):
community_interactions.append("Tease an upcoming live stream on the same topic")
# Export options
st.subheader("Export Options")
export_format = st.selectbox("Export Format", [
"Text",
"Markdown",
"HTML",
"JSON",
"Subtitles (SRT)"
])
custom_filename = st.text_input("Custom Filename (optional)",
placeholder="Leave blank for default filename")
if st.button("Generate Script"):
if not main_points:
st.error("Please enter main points/keywords.")
return
with st.spinner("Generating script..."):
script = generate_youtube_script(
target_audience, main_points, tone_style, use_case, script_structure,
include_hook, include_cta, include_engagement, include_timestamps, include_visual_cues,
engagement_hooks if engagement_hooks else None,
community_interactions if community_interactions else None,
language
)
if script:
# Store the script in session state
st.session_state.generated_script = script
# Store other parameters in session state for regeneration
st.session_state.script_params = {
"target_audience": target_audience,
"main_points": main_points,
"tone_style": tone_style,
"use_case": use_case,
"script_structure": script_structure,
"include_hook": include_hook,
"include_cta": include_cta,
"include_engagement": include_engagement,
"include_timestamps": include_timestamps,
"include_visual_cues": include_visual_cues,
"engagement_hooks": engagement_hooks if engagement_hooks else None,
"community_interactions": community_interactions if community_interactions else None,
"language": language
}
st.subheader("Generated Script")
# Display script with tabs for different views
script_tab1, script_tab2 = st.tabs(["Formatted View", "Plain Text"])
with script_tab1:
st.markdown(script)
with script_tab2:
st.code(script)
# Export options
st.subheader("Export Script")
# Get export data
export_data, export_filename, mime_type = export_script(
script,
export_format,
custom_filename if custom_filename else None
)
# Create columns for the buttons
btn_col1, btn_col2 = st.columns(2)
with btn_col1:
# Download button
st.download_button(
label=f"Download as {export_format}",
data=export_data,
file_name=export_filename,
mime=mime_type
)
with btn_col2:
# Regenerate button
if st.button("Regenerate"):
st.session_state.show_regenerate_popover = True
# Regenerate popover
if st.session_state.get("show_regenerate_popover", False):
with st.form("regenerate_form"):
st.subheader("Regenerate Script")
st.write("Specify changes you'd like to make to the script:")
changes = st.text_area("Changes to make",
placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits")
submitted = st.form_submit_button("Regenerate with Changes")
if submitted and changes:
with st.spinner("Regenerating script..."):
# Get the stored parameters
params = st.session_state.script_params
# Generate a new script with the changes
new_script = generate_youtube_script_with_changes(
params["target_audience"],
params["main_points"],
params["tone_style"],
params["use_case"],
params["script_structure"],
params["include_hook"],
params["include_cta"],
params["include_engagement"],
params["include_timestamps"],
params["include_visual_cues"],
params["engagement_hooks"],
params["community_interactions"],
changes,
params["language"]
)
if new_script:
# Update the stored script
st.session_state.generated_script = new_script
st.session_state.show_regenerate_popover = False
st.rerun()
else:
st.error("Failed to regenerate script. Please try again.")
# Additional export options
if st.checkbox("Show additional export options"):
col1, col2 = st.columns(2)
with col1:
if st.button("Copy to Clipboard"):
st.code(script)
st.success("Script copied to clipboard!")
with col2:
if st.button("Save to Local File"):
# This is a placeholder - actual file saving would require additional backend functionality
st.info("This feature would save the file locally on your device.")
else:
st.error("Failed to generate script. Please try again.")
# Display previously generated script if it exists in session state
elif st.session_state.generated_script:
script = st.session_state.generated_script
params = st.session_state.script_params
st.subheader("Generated Script")
# Display script with tabs for different views
script_tab1, script_tab2 = st.tabs(["Formatted View", "Plain Text"])
with script_tab1:
st.markdown(script)
with script_tab2:
st.code(script)
# Export options
st.subheader("Export Script")
# Get export data
export_data, export_filename, mime_type = export_script(
script,
export_format,
custom_filename if custom_filename else None
)
# Create columns for the buttons
btn_col1, btn_col2 = st.columns(2)
with btn_col1:
# Download button
st.download_button(
label=f"Download as {export_format}",
data=export_data,
file_name=export_filename,
mime=mime_type
)
with btn_col2:
# Regenerate button
if st.button("Regenerate"):
st.session_state.show_regenerate_popover = True
# Regenerate popover
if st.session_state.get("show_regenerate_popover", False):
with st.form("regenerate_form"):
st.subheader("Regenerate Script")
st.write("Specify changes you'd like to make to the script:")
changes = st.text_area("Changes to make",
placeholder="e.g., Make it more casual, add more call-to-actions, focus on product benefits")
submitted = st.form_submit_button("Regenerate with Changes")
if submitted and changes:
with st.spinner("Regenerating script..."):
# Generate a new script with the changes
new_script = generate_youtube_script_with_changes(
params["target_audience"],
params["main_points"],
params["tone_style"],
params["use_case"],
params["script_structure"],
params["include_hook"],
params["include_cta"],
params["include_engagement"],
params["include_timestamps"],
params["include_visual_cues"],
params["engagement_hooks"],
params["community_interactions"],
changes,
params["language"]
)
if new_script:
# Update the stored script
st.session_state.generated_script = new_script
st.session_state.show_regenerate_popover = False
st.rerun()
else:
st.error("Failed to regenerate script. Please try again.")
# Additional export options
if st.checkbox("Show additional export options"):
col1, col2 = st.columns(2)
with col1:
if st.button("Copy to Clipboard"):
st.code(script)
st.success("Script copied to clipboard!")
with col2:
if st.button("Save to Local File"):
# This is a placeholder - actual file saving would require additional backend functionality
st.info("This feature would save the file locally on your device.")

View File

@@ -0,0 +1,314 @@
"""
YouTube Shorts Script Generator Module
This module provides functionality for generating optimized scripts for YouTube Shorts.
"""
import streamlit as st
import time
import logging
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('youtube_shorts_generator')
def generate_shorts_script(hook_type, main_topic, target_audience, tone_style,
content_type, duration_seconds=60, include_captions=True,
include_text_overlay=True, include_sound_effects=False,
vertical_framing_notes=True, language="English"):
"""Generate a YouTube Shorts script optimized for vertical format and short duration."""
# Create a custom system prompt for Shorts script generation
system_prompt = f"""You are a YouTube Shorts expert specializing in creating viral, engaging scripts for vertical short-form videos.
Your task is to generate scripts that are perfectly timed for {duration_seconds} seconds or less.
Focus on hooks that grab attention in the first 1-2 seconds.
Format the script with clear sections for visuals, audio, and text overlays.
Write the entire script in {language}.
Remember that Shorts are viewed vertically (9:16 aspect ratio) and need to work without sound."""
# Build hook-specific instructions
hook_instructions = {
"Question": "Start with an intriguing question that stops the scroll",
"Statistic": "Begin with a surprising statistic or fact",
"Challenge": "Present a challenge or dare to the viewer",
"Tutorial": "Jump straight into a quick how-to or life hack",
"Transformation": "Show a before/after or transformation hook",
"Trend": "Leverage a current trend or sound",
"Story": "Start with a captivating micro-story",
"Controversy": "Present a controversial or surprising statement"
}
# Build the prompt
prompt = f"""
**Instructions:**
Create a YouTube Shorts script about **{main_topic}** with these specifications:
**Core Elements:**
- Hook Type: {hook_type} - {hook_instructions.get(hook_type, "Create an attention-grabbing opening")}
- Target Audience: {target_audience}
- Tone/Style: {tone_style}
- Content Type: {content_type}
- Duration: {duration_seconds} seconds
- Language: {language}
**Required Elements:**
{"- Include caption suggestions for accessibility" if include_captions else ""}
{"- Include text overlay positions and timing" if include_text_overlay else ""}
{"- Include sound effect suggestions" if include_sound_effects else ""}
{"- Include vertical framing notes for optimal composition" if vertical_framing_notes else ""}
**Format the script in this structure:**
1. HOOK (0-2 seconds)
2. MAIN CONTENT (3-50 seconds)
3. CALL TO ACTION (last 10 seconds)
**For each section, specify:**
- Visual Instructions (what to show)
- Text Overlays (what text appears and where)
- Audio/Voiceover
- Timing (in seconds)
- Camera Angles/Framing Notes
**Remember:**
- Scripts must work without sound (many viewers watch on mute)
- Text should be centered in the middle 50% of the vertical frame
- Keep text concise and readable
- Include pattern interrupts every 3-5 seconds
- End with a clear call-to-action
"""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response
except Exception as err:
st.error(f"Error: Failed to get response from LLM: {err}")
return None
def analyze_shorts_script(script):
"""Analyze a Shorts script for optimal engagement metrics."""
analysis = {
'duration_estimate': 0,
'hook_strength': 0,
'pattern_interrupts': 0,
'text_overlay_count': 0,
'readability_score': 0,
'optimization_score': 0
}
# Basic analysis (can be enhanced with more sophisticated metrics)
lines = script.split('\n')
word_count = len(script.split())
# Estimate duration (rough approximation)
analysis['duration_estimate'] = word_count * 0.4 # Average speaking speed
# Count pattern interrupts
analysis['pattern_interrupts'] = script.lower().count('cut to') + script.lower().count('transition')
# Count text overlays
analysis['text_overlay_count'] = script.lower().count('text:') + script.lower().count('overlay:')
# Calculate optimization score
score = 100
# Penalize if estimated duration is too long
if analysis['duration_estimate'] > 60:
score -= (analysis['duration_estimate'] - 60) * 2
# Check for hook presence
if not any(hook in script.lower() for hook in ['hook:', 'opening:', '0-2 seconds:']):
score -= 20
# Check for pattern interrupts (ideal is 1 every 5 seconds)
ideal_interrupts = analysis['duration_estimate'] / 5
if analysis['pattern_interrupts'] < ideal_interrupts:
score -= 10
# Check for text overlay usage
if analysis['text_overlay_count'] < 3:
score -= 10
# Check for call-to-action
if not any(cta in script.lower() for cta in ['call to action', 'cta:', 'subscribe', 'follow']):
score -= 15
analysis['optimization_score'] = max(0, score)
return analysis
def generate_shorts_narration(shorts_script, language="English"):
system_prompt = f"""You are an expert at converting YouTube Shorts scripts into natural, engaging narration.\nYour task is to read the provided Shorts script and output only the narration lines, as they would be spoken in the video.\nOmit all visual instructions, timing, text overlays, and technical cues. Write the narration in {language}."""
prompt = f"""Shorts Script:\n{shorts_script}\n\nInstructions:\nExtract and rewrite only the narration lines, as they would be spoken in the video. Do not include any section headers, cues, or formatting. Output only the narration text."""
try:
response = llm_text_gen(prompt, system_prompt=system_prompt)
return response.strip()
except Exception as err:
st.error(f"Error: Failed to get narration from LLM: {err}")
return ""
def write_yt_shorts():
"""Create a user interface for YouTube Shorts Script Generator."""
st.write("Generate optimized scripts for YouTube Shorts that grab attention and drive engagement.")
# Initialize session state for generated script and active tab if they don't exist
if "generated_shorts_script" not in st.session_state:
st.session_state.generated_shorts_script = None
if "active_tab" not in st.session_state:
st.session_state.active_tab = "Core Elements"
# Create tabs for different sections
tab1, tab2, tab3 = st.tabs(["Core Elements", "Style & Format", "Preview & Export"])
# Set the active tab based on session state
if st.session_state.active_tab == "Core Elements":
tab1.active = True
elif st.session_state.active_tab == "Style & Format":
tab2.active = True
elif st.session_state.active_tab == "Preview & Export":
tab3.active = True
with tab1:
# Core elements
main_topic = st.text_area("Main Topic/Concept",
placeholder="e.g., Quick cooking hack, Life-changing productivity tip")
col1, col2 = st.columns(2)
with col1:
hook_type = st.selectbox("Hook Type", [
"Question",
"Statistic",
"Challenge",
"Tutorial",
"Transformation",
"Trend",
"Story",
"Controversy"
])
target_audience = st.text_input("Target Audience",
placeholder="e.g., Gen Z, busy professionals")
with col2:
content_type = st.selectbox("Content Type", [
"Tutorial/How-to",
"Life Hack",
"Entertainment",
"Educational",
"Trend",
"Story",
"Challenge",
"Review"
])
tone_style = st.selectbox("Tone/Style", [
"Energetic",
"Professional",
"Casual",
"Humorous",
"Dramatic",
"Inspirational"
])
with tab2:
# Style and format options
col1, col2 = st.columns(2)
with col1:
duration_seconds = st.slider("Duration (seconds)", 15, 60, 60)
language = st.selectbox("Language", [
"English",
"Spanish",
"French",
"German",
"Italian",
"Portuguese",
"Russian",
"Japanese",
"Korean",
"Chinese"
])
with col2:
include_captions = st.checkbox("Include Captions", value=True)
include_text_overlay = st.checkbox("Include Text Overlay Positions", value=True)
include_sound_effects = st.checkbox("Include Sound Effects", value=False)
vertical_framing_notes = st.checkbox("Include Vertical Framing Notes", value=True)
with tab3:
if st.session_state.generated_shorts_script:
# Display the generated script
st.subheader("Generated Shorts Script")
# Create tabs for different views
script_tab1, script_tab2, script_tab3 = st.tabs(["Formatted", "Analysis", "Export"])
with script_tab1:
st.markdown(st.session_state.generated_shorts_script)
with script_tab2:
# Analyze the script
analysis = analyze_shorts_script(st.session_state.generated_shorts_script)
# Display analysis results
col1, col2 = st.columns(2)
with col1:
st.metric("Estimated Duration", f"{analysis['duration_estimate']:.1f}s")
st.metric("Pattern Interrupts", analysis['pattern_interrupts'])
st.metric("Text Overlays", analysis['text_overlay_count'])
with col2:
# Display optimization score with color
score = analysis['optimization_score']
color = "red" if score < 60 else "orange" if score < 80 else "green"
st.markdown(f"### Optimization Score: <span style='color: {color}'>{score}%</span>",
unsafe_allow_html=True)
with script_tab3:
# Export options
export_format = st.selectbox("Export Format", [
"Text",
"Markdown",
"Shot List",
"Storyboard"
])
if st.button("Export Script"):
# Implement export functionality based on selected format
st.success(f"Script exported in {export_format} format!")
st.download_button(
"Download Script",
st.session_state.generated_shorts_script,
file_name=f"shorts_script.{export_format.lower()}",
mime="text/plain"
)
# Generate button
if st.button("Generate Shorts Script"):
if not main_topic:
st.error("Please enter a main topic/concept.")
return
with st.spinner("Generating Shorts script..."):
script = generate_shorts_script(
hook_type, main_topic, target_audience, tone_style, content_type,
duration_seconds, include_captions, include_text_overlay,
include_sound_effects, vertical_framing_notes, language
)
if script:
st.session_state.generated_shorts_script = script
# Set active tab to Preview & Export
st.session_state.active_tab = "Preview & Export"
st.success("✨ Script generated successfully! Check the 'Preview & Export' tab to view, analyze, and download your script.")
st.rerun()
else:
st.error("Failed to generate script. Please try again.")
# Add a message about preview and export if script exists but we're not on the Preview tab
if st.session_state.generated_shorts_script and st.session_state.active_tab != "Preview & Export":
st.info("💡 Your generated script is ready! Go to the 'Preview & Export' tab to view, analyze, and download it.")

View File

@@ -0,0 +1,972 @@
"""
YouTube Shorts Video Generator
This module provides functionality to generate YouTube Shorts videos using AI.
It adapts the story video generator for the vertical format and shorter duration of Shorts.
"""
import os
import re
import time
import json
import uuid
import tempfile
import logging
import traceback
from pathlib import Path
from typing import List, Dict, Any, Tuple, Optional, Union, Callable
from functools import wraps
from datetime import datetime
import random
import functools
import streamlit as st
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import requests
# Try importing moviepy with proper error handling
try:
from moviepy.editor import (
ImageSequenceClip,
TextClip,
CompositeVideoClip,
AudioFileClip,
AudioClip,
CompositeAudioClip,
)
MOVIEPY_AVAILABLE = True
except ImportError as e:
st.error(
"MoviePy is not properly installed. Please install it using:\n"
"pip install moviepy imageio imageio-ffmpeg"
)
MOVIEPY_AVAILABLE = False
# Try importing gTTS with proper error handling
try:
from gtts import gTTS
GTTS_AVAILABLE = True
except ImportError:
st.error(
"gTTS is not installed. Please install it using:\n"
"pip install gTTS"
)
GTTS_AVAILABLE = False
# Import LLM text generation and image generation
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
from .shorts_script_generator import generate_shorts_script, generate_shorts_narration
from lib.ai_writers.ai_story_video_generator.story_video_generator import StoryVideoGenerator
# Configure logging
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
log_file = log_dir / f"shorts_generator_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Constants
DEFAULT_FPS = 30 # Higher FPS for smoother Shorts
DEFAULT_DURATION = 2 # seconds per scene (shorter for Shorts)
DEFAULT_TRANSITION_DURATION = 0.5 # seconds for transition
DEFAULT_FONT_SIZE = 32 # Larger font for vertical format
DEFAULT_FONT_COLOR = "white"
DEFAULT_MUSIC_URL = "https://freepd.com/music/Upbeat%20Uplifting%20Corporate.mp3" # Example free music URL
DEFAULT_IMAGE_WIDTH = 1080 # Standard Shorts width
DEFAULT_IMAGE_HEIGHT = 1920 # Standard Shorts height (9:16 aspect ratio)
TEXT_AREA_HEIGHT_RATIO = 1/4 # Smaller text area for vertical format
TEXT_PADDING = 30
TEXT_OVERLAY_ALPHA = 180 # More opaque overlay for better readability
# Shorts-specific constants
MAX_SHORTS_DURATION = 60 # Maximum duration for YouTube Shorts
MIN_SHORTS_DURATION = 15 # Minimum duration for YouTube Shorts
DEFAULT_SHORTS_DURATION = 30 # Default duration for Shorts
MAX_SCENES = 15 # Maximum number of scenes to generate
MIN_SCENES = 5 # Minimum number of scenes
WORDS_PER_SECOND = 2.5 # Average speaking rate for narration
# Video resolutions for Shorts (vertical format)
VIDEO_RESOLUTIONS = {
"1080p": (1080, 1920), # Standard Shorts resolution
"720p": (720, 1280), # Lower resolution option
}
# Transition styles optimized for Shorts
TRANSITION_STYLES = {
"None": None,
"Fade": "fade",
"Slide Up": "slide_up",
"Slide Down": "slide_down",
"Zoom": "zoom",
"Wipe": "wipe"
}
# Content styles for Shorts
CONTENT_STYLES = {
"Tutorial": {
"style": "tutorial",
"description": "Step-by-step instructional content"
},
"Story": {
"style": "story",
"description": "Narrative-driven content"
},
"Tips": {
"style": "tips",
"description": "Quick tips and tricks"
},
"Review": {
"style": "review",
"description": "Product or service reviews"
},
"Behind the Scenes": {
"style": "behind_scenes",
"description": "Behind-the-scenes content"
}
}
# Narration languages
NARRATION_LANGUAGES = {
"English (US)": "en-us",
"English (UK)": "en-gb",
"Spanish": "es",
"French": "fr",
"German": "de",
"Italian": "it",
"Japanese": "ja",
"Korean": "ko",
"Chinese": "zh-cn",
"Hindi": "hi"
}
# Retry configuration
MAX_RETRIES = 3
INITIAL_RETRY_DELAY = 1 # Initial delay in seconds
MAX_RETRY_DELAY = 30 # Maximum delay in seconds
RETRYABLE_ERRORS = (
ConnectionError,
TimeoutError,
requests.exceptions.RequestException,
OSError, # For file system operations
IOError, # For file system operations
)
def retry_on_error(max_retries: int = MAX_RETRIES, initial_delay: int = INITIAL_RETRY_DELAY, max_delay: int = MAX_RETRY_DELAY):
"""
Decorator for retrying functions on specific errors with exponential backoff.
# ... existing code ...
"""
def extract_narration_from_shorts_script(script: str) -> str:
"""
Extract and optimize narration from the script for Shorts format.
Ensures narration is concise, valuable, and properly timed.
"""
scenes = re.split(r'\n\n+', script)
narration_lines = []
total_words = 0
max_words = 75 # Target for 30-second video (2.5 words per second)
# Extract all potential narration lines first
potential_lines = []
for scene in scenes:
match = re.search(r'Audio/Voiceover:\s*(.*)', scene)
if match:
narration = match.group(1).strip()
narration = re.split(r'\n[A-Z][^:]+:', narration)[0].strip()
if narration:
potential_lines.append(narration)
# Process lines to create engaging narration
if potential_lines:
# Start with a hook
first_line = potential_lines[0]
if not any(word in first_line.lower() for word in ['discover', 'learn', 'find out', 'see how', 'watch']):
first_line = f"Discover how to {first_line.lower()}"
narration_lines.append(first_line)
total_words += len(first_line.split())
# Process middle lines
for line in potential_lines[1:-1]:
# Add value-focused phrases
if not any(word in line.lower() for word in ['because', 'why', 'how', 'what', 'when', 'where']):
line = f"Here's why: {line}"
# Check word count
words = line.split()
if total_words + len(words) <= max_words:
narration_lines.append(line)
total_words += len(words)
else:
break
# Add a strong closing
if len(potential_lines) > 1:
last_line = potential_lines[-1]
if not any(phrase in last_line.lower() for phrase in ['try it', 'get started', 'follow for more']):
last_line = f"Ready to try it? {last_line}"
if total_words + len(last_line.split()) <= max_words:
narration_lines.append(last_line)
# If we have too few words, add a call to action
if total_words < 50 and narration_lines:
cta = "Follow for more tips like this!"
if total_words + len(cta.split()) <= max_words:
narration_lines.append(cta)
# Join with proper pacing and emphasis
final_narration = ' '.join(narration_lines)
# Add emphasis to key points
final_narration = re.sub(r'([.!?])\s+', r'\1\n\n', final_narration) # Add pauses
return final_narration
def generate_shorts_narration(script: str, language: str = "en-us", target_duration: int = 30) -> str:
"""
Generate a clean, natural-sounding narration script for YouTube Shorts.
Focuses only on what the listener needs to hear, without technical details.
"""
# Calculate target word count based on duration and user-defined speaking rate
words_per_second = getattr(st.session_state, 'svgen_words_per_second', WORDS_PER_SECOND)
narration_padding = getattr(st.session_state, 'svgen_narration_padding', 0.5)
target_words = int((target_duration - narration_padding) * words_per_second)
# Extract key information from the script
scenes = re.split(r'\n\n+', script)
audio_lines = []
for scene in scenes:
# Extract only the audio/voiceover content
audio_match = re.search(r'Audio/Voiceover:\s*(.*?)(?=\n|$)', scene)
if audio_match:
audio_lines.append(audio_match.group(1).strip())
# Create a specialized prompt for clean narration generation
narration_prompt = f"""
Create a natural, conversational narration script for a YouTube Shorts video.
Focus ONLY on what the listener needs to hear - no technical details, scene descriptions, or timing markers.
Content Context:
{script}
Requirements:
1. Length: {target_duration} seconds (approximately {target_words} words)
2. Style: Natural, conversational, and engaging
3. Structure:
- Start with a hook
- Present key points
- End with a call to action
4. Tone: {st.session_state.svgen_content_style.lower()}
Important Guidelines:
- Write ONLY the spoken words - no descriptions, timing, or technical details
- Use natural language that sounds good when spoken
- Keep sentences short and clear
- Add natural pauses with ellipsis (...)
- No scene numbers, timing markers, or technical instructions
- No sound effect descriptions or music cues
- No formatting markers or special characters
- Target word count: {target_words} words (±10%)
- Speaking rate: {words_per_second} words per second
Example of good narration:
"Writer's block got you down? Meet your new secret weapon: an AI content writer! This tool helps you write ten times faster. No more blank page terror! Blog posts, social media, even killer emails - all generated in seconds. Ready to unleash your content creation superpowers? Try it free today!"
Format the narration as a single, flowing script with natural pauses.
"""
try:
# Generate narration using LLM
narration = llm_text_gen(narration_prompt)
if narration:
# Clean up the narration
narration = re.sub(r'\s+', ' ', narration) # Remove extra spaces
narration = re.sub(r'[^\w\s.,!?…-]', '', narration) # Keep only essential punctuation
narration = re.sub(r'([.!?])\s+', r'\1\n\n', narration) # Add natural pauses
narration = re.sub(r'\*\*.*?\*\*', '', narration) # Remove any markdown
narration = re.sub(r'\(.*?\)', '', narration) # Remove any parenthetical notes
narration = re.sub(r'\n\s*\n', '\n\n', narration) # Clean up extra line breaks
# Verify word count
word_count = len(narration.split())
if word_count < target_words * 0.9 or word_count > target_words * 1.1:
print(f'[WARNING] Generated narration word count ({word_count}) is outside target range ({target_words}±10%)')
return narration.strip()
except Exception as e:
print(f'[ERROR] Failed to generate narration: {e}')
return None
def write_yt_shorts_video():
"""
Main function to generate a YouTube Shorts video.
This function provides a Streamlit interface for users to generate Shorts videos.
"""
st.markdown("""
<style>
.stepper {
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
}
.step {
flex: 1;
text-align: center;
padding: 0.5rem 0;
border-bottom: 4px solid #e0e0e0;
color: #888;
font-weight: 600;
font-size: 1.1rem;
}
.step.active {
color: #2563eb;
border-bottom: 4px solid #2563eb;
background: #f0f6ff;
border-radius: 8px 8px 0 0;
}
.card {
background: #f8fafc;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
padding: 2rem 2rem 1.5rem 2rem;
margin-bottom: 2rem;
}
.section-title {
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 1rem;
color: #222;
display: flex;
align-items: center;
}
.section-title svg {
margin-right: 0.5rem;
}
.primary-btn {
background: #2563eb;
color: #fff;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
padding: 0.75rem 2.5rem;
border: none;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
box-shadow: 0 2px 8px rgba(37,99,235,0.08);
}
</style>
""", unsafe_allow_html=True)
# Stepper logic
if 'shorts_stage' not in st.session_state:
st.session_state.shorts_stage = 1
if 'generated_script' not in st.session_state:
st.session_state.generated_script = None
if 'script_approved' not in st.session_state:
st.session_state.script_approved = False
# Stepper UI
st.markdown(f'''
<div class="stepper">
<div class="step {'active' if st.session_state.shorts_stage == 1 else ''}">1. Input Details</div>
<div class="step {'active' if st.session_state.shorts_stage == 2 else ''}">2. Script Review</div>
<div class="step {'active' if st.session_state.shorts_stage == 3 else ''}">3. Video Generation</div>
</div>
''', unsafe_allow_html=True)
# --- Stage 1: Input Details ---
if st.session_state.shorts_stage == 1:
print('[DEBUG] Stage 1: Input Details loaded')
st.markdown('---')
st.markdown('### 1⃣ Input Video Details')
st.info("Fill in all details below, then click **Generate Script** to continue.")
with st.container():
st.markdown('<div class="card">', unsafe_allow_html=True)
st.markdown('<div class="section-title">📝 Video Content</div>', unsafe_allow_html=True)
video_topic = st.text_input(
"What's your video about?",
placeholder="Enter the main topic or theme of your Shorts video",
help="Be specific about what you want to create"
)
style_col, duration_col = st.columns(2)
with style_col:
content_style = st.selectbox(
"Content Style",
list(CONTENT_STYLES.keys()),
help="Select the style that best fits your content"
)
with duration_col:
video_duration = st.slider(
"Duration (seconds)",
MIN_SHORTS_DURATION,
MAX_SHORTS_DURATION,
DEFAULT_SHORTS_DURATION,
help=f"Shorts must be between {MIN_SHORTS_DURATION} and {MAX_SHORTS_DURATION} seconds"
)
# Calculate and display scene count based on duration
scene_duration = DEFAULT_DURATION # seconds per scene
max_possible_scenes = min(MAX_SCENES, int(video_duration / scene_duration))
min_possible_scenes = max(MIN_SCENES, int(video_duration / (scene_duration * 2)))
scene_count = st.slider(
"Number of Scenes",
min_possible_scenes,
max_possible_scenes,
min(max_possible_scenes, 10), # Default to 10 or max possible
help=f"Based on {scene_duration}s per scene, you can have {min_possible_scenes}-{max_possible_scenes} scenes"
)
st.markdown('</div>', unsafe_allow_html=True)
with st.container():
settings_col = st.columns(1)[0]
with settings_col:
with st.expander("⚙️ Video Settings", expanded=True):
res_col, trans_col = st.columns(2)
with res_col:
resolution = st.selectbox(
"Resolution",
list(VIDEO_RESOLUTIONS.keys()),
help="Higher resolution = better quality but longer processing time"
)
with trans_col:
transition_style = st.selectbox(
"Transition Style",
list(TRANSITION_STYLES.keys()),
help="How scenes transition between each other"
)
# Add timing controls
st.markdown("---")
st.markdown("#### ⏱️ Timing Settings")
# Scene timing controls
timing_col1, timing_col2 = st.columns(2)
with timing_col1:
scene_duration = st.slider(
"Seconds per Scene",
min_value=1.0,
max_value=5.0,
value=DEFAULT_DURATION,
step=0.5,
help="How long each scene should be displayed"
)
st.session_state.svgen_scene_duration = scene_duration
with timing_col2:
transition_duration = st.slider(
"Transition Duration (seconds)",
min_value=0.1,
max_value=1.0,
value=DEFAULT_TRANSITION_DURATION,
step=0.1,
help="Duration of transitions between scenes"
)
st.session_state.svgen_transition_duration = transition_duration
# Narration timing controls
narr_timing_col1, narr_timing_col2 = st.columns(2)
with narr_timing_col1:
words_per_second = st.slider(
"Speaking Rate (words/second)",
min_value=1.5,
max_value=3.5,
value=WORDS_PER_SECOND,
step=0.1,
help="Adjust narration speed (default: 2.5 words/second)"
)
st.session_state.svgen_words_per_second = words_per_second
with narr_timing_col2:
narration_padding = st.slider(
"Narration Padding (seconds)",
min_value=0.0,
max_value=2.0,
value=0.5,
step=0.1,
help="Extra time to add to narration duration"
)
st.session_state.svgen_narration_padding = narration_padding
# Calculate and display timing information
total_scene_time = scene_duration * scene_count
total_transition_time = transition_duration * (scene_count - 1)
total_video_time = total_scene_time + total_transition_time
st.info(f"""
**Timing Summary:**
- Total Scene Time: {total_scene_time:.1f}s
- Total Transition Time: {total_transition_time:.1f}s
- Estimated Video Duration: {total_video_time:.1f}s
- Target Narration Length: {int(total_video_time * words_per_second)} words
""")
with st.expander("🎙️ Narration Settings", expanded=True):
narr_col1, narr_col2 = st.columns(2)
with narr_col1:
narration_language = st.selectbox(
"Language",
list(NARRATION_LANGUAGES.keys()),
help="Select the language for narration"
)
with narr_col2:
include_music = st.checkbox(
"Include Background Music",
value=True,
help="Add background music to enhance the video"
)
st.markdown('---')
can_generate_script = bool(video_topic and content_style and video_duration and resolution and narration_language)
if st.button("📝 Generate Script", key="generate_script_btn", help="Generate a script for your Shorts video", use_container_width=True, disabled=not can_generate_script):
print(f'[DEBUG] Generate Script button clicked. Topic: {video_topic}, Style: {content_style}, Duration: {video_duration}, Resolution: {resolution}, Language: {narration_language}')
try:
with st.spinner("Generating script..."):
script = generate_shorts_script(
hook_type="Question",
main_topic=video_topic,
target_audience="general",
tone_style=content_style,
content_type=CONTENT_STYLES[content_style]["style"],
duration_seconds=video_duration,
include_captions=True,
include_text_overlay=True,
include_sound_effects=True,
vertical_framing_notes=True,
language=narration_language
)
print(f'[DEBUG] Script generated: {bool(script)}')
if script:
st.session_state.generated_script = script
st.session_state.script_approved = False
st.session_state.shorts_stage = 2
st.session_state.svgen_resolution = resolution
st.session_state.svgen_transition_style = transition_style
st.session_state.svgen_narration_language = narration_language
st.session_state.svgen_include_music = include_music
st.session_state.svgen_content_style = content_style
st.session_state.svgen_video_duration = video_duration
st.session_state.svgen_video_topic = video_topic
print('[DEBUG] Script saved to session state and moving to Stage 2')
st.success("Script generated! Review and edit below.")
else:
print('[ERROR] Script generation failed')
st.error("Failed to generate script. Please try again.")
except Exception as e:
print(f'[ERROR] Exception during script generation: {e}')
st.error(f"An error occurred while generating the script: {str(e)}")
logger.error(f"Error in script generation: {str(e)}")
logger.error(traceback.format_exc())
if not can_generate_script:
st.warning("Please fill in all required fields above to enable script generation.")
st.markdown('---')
st.info("Next: Review and edit your script.")
# --- Stage 2: Script Review & Edit ---
if st.session_state.shorts_stage == 2:
print('[DEBUG] Stage 2: Script Review & Edit loaded')
st.markdown('---')
st.markdown('### 2⃣ Script Review & Edit')
st.info("Review your generated script. Use the Edit tab to make changes. Approve to continue.")
st.markdown('<div class="card">', unsafe_allow_html=True)
st.markdown('<div class="section-title">📄 Script Preview & Edit</div>', unsafe_allow_html=True)
preview_tab, edit_tab = st.tabs(["Preview", "Edit"])
with preview_tab:
st.markdown(st.session_state.generated_script)
if not st.session_state.script_approved:
if st.button("✅ Approve Script", key="approve_script_btn", use_container_width=True):
st.session_state.script_approved = True
print('[DEBUG] Script approved by user')
st.success("Script approved! You can now generate your video.")
with edit_tab:
edited_script = st.text_area(
"Edit Script",
value=st.session_state.generated_script,
height=400,
help="Make any necessary changes to the script. The format should be maintained."
)
if edited_script != st.session_state.generated_script:
print('[DEBUG] Script edited by user')
st.session_state.generated_script = edited_script
st.session_state.script_approved = False
st.info("Script updated. Please review and approve the changes.")
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('---')
st.button("⬅️ Back to Details", key="back_to_details_btn", use_container_width=True, on_click=lambda: st.session_state.update({'shorts_stage': 1}))
if st.session_state.script_approved:
st.success("Script approved! You can now generate your video.")
st.button("🎬 Proceed to Video Generation", key="proceed_to_video_btn", use_container_width=True, on_click=lambda: st.session_state.update({'shorts_stage': 3}))
else:
st.warning("Please approve your script before proceeding.")
st.markdown('---')
st.info("Next: Review and edit narration, then generate your video.")
# --- Stage 3: Video Generation ---
if st.session_state.shorts_stage == 3:
print('[DEBUG] Stage 3: Narration & Video Generation loaded')
st.markdown('---')
st.markdown('### 3⃣ Narration & Video Generation')
st.info("Edit or generate narration, preview audio, then click **Generate Video**.")
st.markdown('<div class="card">', unsafe_allow_html=True)
st.markdown('<div class="section-title">🗣️ Narration for Review & Edit</div>', unsafe_allow_html=True)
narr_col1, narr_col2 = st.columns([4, 1])
with narr_col1:
if 'editable_narration' not in st.session_state:
st.session_state.editable_narration = generate_shorts_narration(
st.session_state.generated_script,
language=st.session_state.svgen_narration_language,
target_duration=st.session_state.svgen_video_duration
)
print('[DEBUG] Initial narration generated')
edited_narration = st.text_area(
"Edit narration to be used for TTS:",
value=st.session_state.editable_narration,
height=120,
key="editable_narration_area",
help="Edit the narration to sound natural when spoken. No technical details needed."
)
st.session_state.editable_narration = edited_narration
# Calculate and display timing information
narration_word_count = len(edited_narration.split())
words_per_second = 2.5 # Standard speaking rate
estimated_duration = narration_word_count / words_per_second
narration_stats = (
f"Words: {narration_word_count} | "
f"Est. duration: {estimated_duration:.1f}s | "
f"Target: {st.session_state.svgen_video_duration}s"
)
st.caption(narration_stats)
# Display timing warnings
if estimated_duration < 20:
st.warning("⚠️ Narration is too short for a 30-second video. Consider generating a new narration.")
elif estimated_duration > 35:
st.warning("⚠️ Narration is too long for a 30-second video. Consider generating a new narration.")
# Narration Tips in an expander
with st.expander("💡 Narration Tips", expanded=False):
st.markdown("""
### Tips for Natural Narration
- Write only what should be spoken
- Keep it conversational and clear
- Use natural pauses (...)
- Focus on the message, not the technical details
- End with a clear call to action
""")
tts_col1, tts_col2 = st.columns(2)
with tts_col1:
tts_gender = st.selectbox("Voice Gender (affects some TTS engines)", ["Default", "Female", "Male"], key="tts_gender_select")
with tts_col2:
tts_speed = st.selectbox("Speech Speed", ["Normal", "Slow"], key="tts_speed_select")
if st.button("🔊 Preview Narration Audio", key="preview_tts_btn"):
print('[DEBUG] TTS preview button clicked')
try:
tts_kwargs = {"lang": NARRATION_LANGUAGES[st.session_state.svgen_narration_language]}
tts_kwargs["slow"] = tts_speed == "Slow"
tts = gTTS(text=edited_narration, **tts_kwargs)
preview_audio_path = os.path.join(tempfile.gettempdir(), f"tts_preview_{os.getpid()}.mp3")
tts.save(preview_audio_path)
with open(preview_audio_path, "rb") as audio_file:
audio_bytes = audio_file.read()
st.audio(audio_bytes, format="audio/mp3")
print('[DEBUG] TTS preview audio generated and played')
except Exception as tts_err:
print(f'[ERROR] Failed to generate TTS preview: {tts_err}')
st.error(f"Failed to generate TTS preview: {tts_err}")
if narration_word_count < 10:
st.warning("Narration is very short. Consider adding more detail.")
elif narration_word_count > 120:
st.warning("Narration is quite long. Consider shortening for Shorts.")
with narr_col2:
if st.button("🔄 Generate New Narration", key="generate_narration_btn"):
with st.spinner("Generating engaging narration..."):
new_narration = generate_shorts_narration(
st.session_state.generated_script,
language=st.session_state.svgen_narration_language,
target_duration=st.session_state.svgen_video_duration
)
if new_narration:
st.session_state.editable_narration = new_narration
print('[DEBUG] New narration generated')
st.success("New narration generated successfully!")
st.rerun()
else:
st.error("Failed to generate narration. Please try again.")
if st.button("🤖 Generate AI Narration", key="ai_narration_btn"):
with st.spinner("Generating AI-optimized narration..."):
ai_narr = generate_shorts_narration(
st.session_state.generated_script,
language=st.session_state.svgen_narration_language,
target_duration=st.session_state.svgen_video_duration
)
if ai_narr:
st.session_state.editable_narration = ai_narr
print('[DEBUG] AI-generated narration updated')
st.success("AI-generated narration updated.")
st.rerun()
else:
st.error("Failed to generate AI narration. Please try again.")
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('---')
st.markdown('### 3⃣ Video Generation')
st.info("Click **Generate Video** to start the final process. This may take a few minutes.")
st.markdown('<div class="card">', unsafe_allow_html=True)
st.markdown('<div class="section-title"> Video Generation</div>', unsafe_allow_html=True)
# Video Information in an expander
with st.expander("📋 Video Information", expanded=True):
st.markdown("""
### Video Details
| Setting | Value |
|---------|--------|
| Video Topic | {} |
| Content Style | {} |
| Duration | {} seconds |
| Resolution | {} |
| Narration Language | {} |
| Background Music | {} |
""".format(
st.session_state.svgen_video_topic,
st.session_state.svgen_content_style,
st.session_state.svgen_video_duration,
st.session_state.svgen_resolution,
st.session_state.svgen_narration_language,
"Yes" if st.session_state.svgen_include_music else "No"
))
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('<div style="text-align:center">', unsafe_allow_html=True)
st.button("⬅️ Back to Script Review", key="back_to_script_btn", use_container_width=True, on_click=lambda: st.session_state.update({'shorts_stage': 2}))
if st.button("🚀 Generate Video", key="generate_video_btn", use_container_width=True):
print('[DEBUG] Generate Video button clicked')
try:
with st.spinner("Generating your Shorts video..."):
st.info("Step 1/3: Generating images...")
image_paths = []
temp_dir = Path(tempfile.mkdtemp())
# Filter out empty scenes and limit to MAX_SCENES
scenes = [s.strip() for s in st.session_state.generated_script.split("\n\n") if s.strip()][:MAX_SCENES]
resolution = st.session_state.svgen_resolution
narration_language = st.session_state.svgen_narration_language
scene_count = 0
num_scenes_total = len(scenes)
progress_bar = st.progress(0.0)
status_text = st.empty()
# Initialize or load image cache
if 'generated_image_paths' not in st.session_state:
st.session_state.generated_image_paths = {}
generated_image_paths = st.session_state.generated_image_paths
# Clear any invalid cache entries
generated_image_paths = {k: v for k, v in generated_image_paths.items()
if os.path.exists(v) and k < num_scenes_total}
st.session_state.generated_image_paths = generated_image_paths
preview_container = st.container()
preview_thumbnails = []
def retry_on_error(max_retries=3, initial_delay=1, max_delay=10):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
delay = initial_delay
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_retries - 1:
raise
print(f'[WARN] Retry {attempt+1}/{max_retries} for image generation: {e}')
time.sleep(delay)
delay = min(delay * 2, max_delay)
return None
return wrapper
return decorator
@retry_on_error(max_retries=3, initial_delay=2, max_delay=10)
def safe_generate_image(prompt):
return generate_image(prompt)
for i, scene in enumerate(scenes):
print(f'[DEBUG] Processing scene {i+1}/{num_scenes_total}')
status_text.text(f"Generating image for scene {i+1}/{num_scenes_total}...")
# Check cache first
if i in generated_image_paths:
image_paths.append(generated_image_paths[i])
preview_thumbnails.append((generated_image_paths[i], i+1))
print(f'[DEBUG] Using cached image for scene {i+1}')
scene_count += 1
progress_bar.progress(scene_count / num_scenes_total)
continue
# Extract details for a more specific prompt
visual_desc = scene.split("Visual Instructions:")[1].split("\n")[0] if "Visual Instructions:" in scene else scene
narration_match = re.search(r'Audio/Voiceover:\s*(.*)', scene)
narration_line = narration_match.group(1).strip() if narration_match else ""
# Enhanced prompt with more specific details and style guidance
prompt = (
f"Create a vertical (9:16) image for YouTube Shorts video.\n"
f"Scene {i+1} of {num_scenes_total}:\n"
f"Visual Description: {visual_desc}\n"
f"Context: {narration_line}\n"
f"Style Requirements:\n"
f"- High contrast and vibrant colors for better mobile viewing\n"
f"- Clear focal point in the center for vertical format\n"
f"- Professional quality, cinematic lighting\n"
f"- Text-safe areas on top and bottom\n"
f"- Visually distinct from other scenes\n"
f"- Modern, engaging composition\n"
f"- Suitable for {st.session_state.svgen_content_style} style content\n"
f"Technical Requirements:\n"
f"- Vertical 9:16 aspect ratio\n"
f"- High resolution, sharp details\n"
f"- No text or watermarks\n"
f"- No blurry or low-quality elements"
)
try:
image_path = safe_generate_image(prompt)
if image_path:
img = Image.open(image_path)
target_size = VIDEO_RESOLUTIONS[resolution]
img = img.resize(target_size, Image.LANCZOS)
resized_path = temp_dir / f"scene_{i}.png"
img.save(resized_path)
image_paths.append(str(resized_path))
generated_image_paths[i] = str(resized_path)
st.session_state.generated_image_paths = generated_image_paths
preview_thumbnails.append((str(resized_path), i+1))
print(f'[DEBUG] Generated and cached new image for scene {i+1}')
else:
print(f'[ERROR] Image generation failed for scene {i+1}')
st.warning(f"Image generation failed for scene {i+1}. Skipping.")
except Exception as img_err:
print(f'[ERROR] Exception during image generation for scene {i+1}: {img_err}')
st.warning(f"Error generating image for scene {i+1}: {img_err}")
scene_count += 1
progress_bar.progress(scene_count / num_scenes_total)
# Update preview after each image
with preview_container:
preview_container.empty() # Clear previous preview
if preview_thumbnails:
# Create a grid layout with 5 columns
cols = st.columns(5)
# Display thumbnails in a grid
for idx, (img_path, sc_num) in enumerate(preview_thumbnails):
with cols[idx % 5]:
# Create a smaller thumbnail
img = Image.open(img_path)
# Calculate aspect ratio to maintain 9:16
target_width = 100 # Smaller width
target_height = int(target_width * (16/9))
img = img.resize((target_width, target_height), Image.LANCZOS)
# Display with a compact caption
st.image(
img,
caption=f"Scene {sc_num}",
use_column_width=True,
key=f"preview_{sc_num}" # Add unique key for each image
)
# Add a small progress indicator
if idx == len(preview_thumbnails) - 1:
st.caption(f"Generating scene {scene_count + 1}...")
# Add a clear divider between preview and next steps
st.markdown("---")
status_text.text("Image generation complete!")
print(f'[DEBUG] Image generation complete. Total images: {len(image_paths)}')
if not image_paths:
print('[ERROR] No images generated')
st.error("Failed to generate images. Please try again.")
return
st.info("Step 2/3: Generating narration...")
narration_path = temp_dir / "narration.mp3"
narration_text = st.session_state.editable_narration
try:
tts = gTTS(text=narration_text, lang=NARRATION_LANGUAGES[narration_language])
tts.save(str(narration_path))
print('[DEBUG] Narration audio generated and saved')
# Verify the audio file was created and is valid
if not os.path.exists(str(narration_path)):
raise Exception("Narration audio file was not created")
# Test the audio file by loading it
test_audio = AudioFileClip(str(narration_path))
if test_audio.duration <= 0:
raise Exception("Generated audio file is invalid or empty")
test_audio.close()
except Exception as tts_err:
print(f'[ERROR] Failed to generate narration: {tts_err}')
st.error(f"Failed to generate narration: {tts_err}")
return
st.info("Step 3/3: Creating video...")
video_generator = StoryVideoGenerator()
try:
# Verify audio file exists before video creation
if not os.path.exists(str(narration_path)):
raise Exception("Narration audio file not found")
video_path = video_generator.create_video(
image_paths=image_paths,
audio_path=str(narration_path),
fps=DEFAULT_FPS,
duration_per_image=getattr(st.session_state, 'svgen_scene_duration', DEFAULT_DURATION)
)
if video_path and os.path.exists(video_path):
print(f'[DEBUG] Video generated at {video_path}')
st.success("✨ Video generated successfully! Preview below and download your video.")
st.video(video_path)
safe_topic = re.sub(r'[^\w\-]+', '_', st.session_state.svgen_video_topic)
download_filename = f"{safe_topic}_shorts_video.mp4"
with open(video_path, "rb") as f:
video_bytes = f.read()
st.download_button(
label="⬇️ Download Video",
data=video_bytes,
file_name=download_filename,
mime="video/mp4"
)
else:
print('[ERROR] Video file not found after generation')
st.error("Failed to create video. Please try again.")
except Exception as vid_err:
print(f'[ERROR] Exception during video creation: {vid_err}')
st.error(f"An error occurred while creating the video: {vid_err}")
logger.error(f"Error in video generation: {vid_err}")
logger.error(traceback.format_exc())
except Exception as e:
print(f'[ERROR] Exception during full video generation: {e}')
st.error(f"An error occurred while generating the video: {str(e)}")
logger.error(f"Error in video generation: {str(e)}")
logger.error(traceback.format_exc())
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('---')
st.info("All done! You can download your video above or go back to make changes.")

View File

@@ -0,0 +1,406 @@
"""
YouTube Tags Generator Module
This module provides functionality for generating and optimizing YouTube video tags.
"""
import streamlit as st
import time
import logging
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from pytrends.request import TrendReq
import pandas as pd
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('youtube_tags_generator')
def get_pytrends_data(keyword):
"""Get trending data using PyTrends with simplified, reliable approach."""
logger.info(f"Getting PyTrends data for: '{keyword}'")
# Initialize empty results
results = {
'topics': [],
'queries': [],
'trending': []
}
try:
# Initialize PyTrends with minimal configuration
pytrends = TrendReq(hl='en-US', tz=360)
time.sleep(1) # Basic rate limiting
# 1. Get suggestions (most reliable method)
try:
suggestions = pytrends.suggestions(keyword)
if suggestions:
results['trending'] = [sugg['title'] for sugg in suggestions if sugg['title']][:3]
except Exception as e:
logger.warning(f"Error getting suggestions: {str(e)}")
# 2. Get trending searches as backup
if not results['trending']:
try:
trending = pytrends.trending_searches(pn='united_states')
if not trending.empty:
results['trending'] = trending.head(3).values.tolist()
except Exception as e:
logger.warning(f"Error getting trending searches: {str(e)}")
# 3. Use keyword variations as fallback
if not any(results.values()):
results['trending'] = [keyword]
results['queries'] = [keyword.lower(), keyword.title()]
results['topics'] = [keyword.capitalize()]
return results
except Exception as e:
logger.error(f"Error in PyTrends: {str(e)}")
# Return basic keyword variations as fallback
return {
'topics': [keyword.capitalize()],
'queries': [keyword.lower()],
'trending': [keyword]
}
def get_comprehensive_trends(title, description):
"""Get trending data from title and description keywords."""
logger.info(f"Getting comprehensive trends for title: '{title}'")
# Extract main keywords (only words longer than 3 chars)
words = [w for w in title.split() if len(w) > 3]
if description:
desc_words = [w for w in description.split() if len(w) > 3]
words.extend(desc_words)
# Remove duplicates and limit to 2 keywords to prevent rate limiting
keywords = list(dict.fromkeys(words))[:2]
# Get trending data for main keywords
all_trends = {
'topics': [],
'queries': [],
'trending': []
}
for keyword in keywords:
try:
trends = get_pytrends_data(keyword)
for key in all_trends:
if trends[key]:
all_trends[key].extend(trends[key])
time.sleep(1) # Rate limiting between keywords
except Exception as e:
logger.warning(f"Error getting trends for keyword '{keyword}': {str(e)}")
continue
# Remove duplicates while preserving order
for key in all_trends:
seen = set()
all_trends[key] = [x for x in all_trends[key] if x and not (x.lower() in seen or seen.add(x.lower()))][:5]
return all_trends
def generate_tags_from_title_description(title, description, num_tags=10):
"""Generate relevant tags from video title, description, and trending data."""
logger.info(f"Generating tags for title: '{title}'")
# Get comprehensive trending data
trends = get_comprehensive_trends(title, description)
# Create a comprehensive context for GPT
trend_context = f"""
Related Topics: {', '.join(trends['topics'][:10])}
Related Queries: {', '.join(trends['queries'][:10])}
Trending Suggestions: {', '.join(trends['trending'][:10])}
"""
system_prompt = """You are a YouTube SEO expert specializing in tag optimization.
Generate relevant, searchable tags based on the video title, description, and trending data provided.
Focus on a mix of specific and broad tags that will help with video discovery.
Consider the trending topics and queries provided to maximize searchability.
Return only the tags, separated by commas."""
user_prompt = f"""Generate {num_tags} relevant YouTube tags for a video with:
Title: {title}
Description: {description}
Consider this trending data:
{trend_context}
Include a mix of:
- Exact match phrases from title and description
- Related trending topics and queries
- Broader category tags
- Specific niche tags
- Popular search variations
Format: Return only the tags, separated by commas."""
try:
tags = llm_text_gen(user_prompt, system_prompt=system_prompt)
generated_tags = [tag.strip() for tag in tags.split(',')]
# Add some trending tags directly
trending_tags = (
trends['topics'][:3] + # Top 3 related topics
trends['queries'][:3] + # Top 3 related queries
trends['trending'][:3] # Top 3 trending suggestions
)
# Combine and remove duplicates
all_tags = generated_tags + trending_tags
seen = set()
final_tags = [tag for tag in all_tags if not (tag.lower() in seen or seen.add(tag.lower()))]
return final_tags
except Exception as e:
logger.error(f"Error generating tags: {str(e)}")
return []
def analyze_tags(tags):
"""Analyze tags for optimization opportunities."""
analysis = {
'total_tags': len(tags),
'total_characters': sum(len(tag) for tag in tags),
'avg_tag_length': sum(len(tag) for tag in tags) / len(tags) if tags else 0,
'duplicate_tags': len(tags) - len(set(tags)),
'tags_too_long': [tag for tag in tags if len(tag) > 30],
'single_word_tags': [tag for tag in tags if len(tag.split()) == 1],
'optimization_score': 0
}
# Calculate optimization score (0-100)
score = 100
if analysis['total_tags'] < 5:
score -= 30
if analysis['total_characters'] > 500:
score -= 20
if analysis['duplicate_tags'] > 0:
score -= 10 * analysis['duplicate_tags']
if len(analysis['tags_too_long']) > 0:
score -= 5 * len(analysis['tags_too_long'])
if len(analysis['single_word_tags']) > len(tags) * 0.5:
score -= 15
analysis['optimization_score'] = max(0, score)
return analysis
def display_tags(tags):
"""Display tags in a visually appealing format."""
if not tags:
return
# Create a container for all tags
st.markdown("""
<style>
.tag-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
padding: 12px;
background-color: #f8f9fa;
border-radius: 8px;
}
.tag {
display: inline-flex;
align-items: center;
background-color: #f0f2f6;
border-radius: 16px;
padding: 6px 12px;
font-size: 13px;
color: #2c3e50;
border: 1px solid #e6e9ef;
white-space: nowrap;
transition: all 0.2s ease;
}
.tag:hover {
background-color: #e6e9ef;
border-color: #d1d5db;
transform: translateY(-1px);
}
</style>
<div class="tag-container">
""", unsafe_allow_html=True)
# Display tags
for tag in tags:
st.markdown(f'<div class="tag">{tag}</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Display tag count and character count
tags_text = ", ".join(tags)
char_count = len(tags_text)
col1, col2 = st.columns(2)
with col1:
st.caption(f"Total tags: {len(tags)}")
with col2:
st.caption(f"Characters: {char_count}/500")
def write_yt_tags():
"""Create a user interface for YouTube Tags Generator."""
logger.info("Initializing YouTube Tags Generator UI")
st.write("Generate optimized tags for your videos with trending tag suggestions to improve discoverability.")
# Initialize session state
if "generated_tags" not in st.session_state:
st.session_state.generated_tags = None
if "tag_analysis" not in st.session_state:
st.session_state.tag_analysis = None
# Create tabs for different sections
tab1, tab2, tab3 = st.tabs(["Quick Generate", "Advanced Options", "Analysis"])
with tab1:
# Basic information inputs
title = st.text_input("Video Title",
placeholder="Enter your video title")
description = st.text_area("Video Description",
placeholder="Enter your video description")
col1, col2 = st.columns(2)
with col1:
num_tags = st.number_input("Number of Tags",
min_value=5,
max_value=30,
value=15)
with col2:
include_trending = st.checkbox("Include Trending Suggestions", value=True)
if st.button("Generate Tags"):
if not title:
st.error("Please enter a video title.")
return
with st.spinner("Generating tags..."):
# Generate tags using the comprehensive method
tags = generate_tags_from_title_description(title, description, num_tags)
if tags:
# Analyze tags
st.session_state.tag_analysis = analyze_tags(tags)
st.session_state.generated_tags = tags
# Display tags in the new format
st.subheader("Generated Tags")
display_tags(tags)
# Add copy button for all tags
tags_text = ", ".join(tags)
st.text_area("Tags (copy to use)", value=tags_text, height=100)
# Display character count
char_count = len(tags_text)
st.info(f"Total characters: {char_count}/500 ({500 - char_count} remaining)")
# Quick analysis summary
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Number of Tags", len(tags))
with col2:
st.metric("Optimization Score", f"{st.session_state.tag_analysis['optimization_score']}%")
with col3:
st.metric("Avg Tag Length", f"{st.session_state.tag_analysis['avg_tag_length']:.1f}")
# Display trending data summary if enabled
if include_trending:
st.subheader("Trending Data Used")
trends = get_comprehensive_trends(title, description)
# Create columns for different trend types
tcol1, tcol2, tcol3 = st.columns(3)
with tcol1:
st.markdown("##### Related Topics")
if trends['topics']:
for topic in trends['topics'][:5]:
st.markdown(f"{topic}")
else:
st.markdown("*No related topics found*")
with tcol2:
st.markdown("##### Related Queries")
if trends['queries']:
for query in trends['queries'][:5]:
st.markdown(f"{query}")
else:
st.markdown("*No related queries found*")
with tcol3:
st.markdown("##### Trending Suggestions")
if trends['trending']:
for trend in trends['trending'][:5]:
st.markdown(f"{trend}")
else:
st.markdown("*No trending suggestions found*")
else:
st.error("Failed to generate tags. Please try again.")
with tab2:
st.info("Advanced tag generation options coming soon!")
st.markdown("""
Future features will include:
- Competitor tag analysis
- Tag performance tracking
- Category-specific tag suggestions
- Multi-language tag generation
- Tag sets management
""")
with tab3:
if st.session_state.tag_analysis:
st.subheader("Tag Analysis")
# Create metrics
col1, col2 = st.columns(2)
with col1:
st.metric("Total Tags", st.session_state.tag_analysis['total_tags'])
st.metric("Total Characters", st.session_state.tag_analysis['total_characters'])
st.metric("Average Tag Length", f"{st.session_state.tag_analysis['avg_tag_length']:.1f}")
with col2:
st.metric("Duplicate Tags", st.session_state.tag_analysis['duplicate_tags'])
st.metric("Single Word Tags", len(st.session_state.tag_analysis['single_word_tags']))
st.metric("Tags Too Long", len(st.session_state.tag_analysis['tags_too_long']))
# Optimization score with color
score = st.session_state.tag_analysis['optimization_score']
score_color = 'red' if score < 50 else 'orange' if score < 80 else 'green'
st.markdown(f"""
<div style='background-color: {score_color}; padding: 10px; border-radius: 5px; margin: 10px 0;'>
<h3 style='color: white; margin: 0;'>Optimization Score: {score}%</h3>
</div>
""", unsafe_allow_html=True)
# Optimization suggestions
st.subheader("Optimization Suggestions")
suggestions = []
if st.session_state.tag_analysis['total_tags'] < 5:
suggestions.append("❌ Add more tags (aim for at least 15)")
if st.session_state.tag_analysis['total_characters'] > 500:
suggestions.append("❌ Total character count exceeds limit (max 500)")
if st.session_state.tag_analysis['duplicate_tags'] > 0:
suggestions.append("❌ Remove duplicate tags")
if len(st.session_state.tag_analysis['tags_too_long']) > 0:
suggestions.append("❌ Some tags are too long (max 30 characters)")
if len(st.session_state.tag_analysis['single_word_tags']) > st.session_state.tag_analysis['total_tags'] * 0.5:
suggestions.append("❌ Too many single-word tags (use more specific phrases)")
if not suggestions:
st.success("✅ Your tags are well-optimized!")
else:
for suggestion in suggestions:
st.warning(suggestion)
else:
st.info("Generate tags first to see analysis")

View File

@@ -0,0 +1,622 @@
"""
YouTube Thumbnail Generator Module
This module provides functionality for generating YouTube video thumbnails.
"""
import streamlit as st
import time
import logging
import os
import traceback
from PIL import Image
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
from lib.gpt_providers.text_to_image_generation.gen_gemini_images import generate_gemini_image, edit_image
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('youtube_thumbnail_generator')
def generate_thumbnail_concepts(video_title, video_description, target_audience, content_type, style_preference, num_concepts=3):
"""Generate thumbnail concept ideas based on video content."""
logger.info(f"Generating thumbnail concepts for: '{video_title}'")
logger.info(f"Parameters: target_audience={target_audience}, content_type={content_type}, style_preference={style_preference}, num_concepts={num_concepts}")
# Create a system prompt for thumbnail concept generation
system_prompt = """You are a YouTube thumbnail expert specializing in creating engaging, click-worthy thumbnail concepts.
Your task is to generate thumbnail concept ideas based on the provided video information.
Focus ONLY on creating concepts that are optimized for YouTube, with proper visual hierarchy, text placement, and emotional triggers.
Return ONLY the concept descriptions, without any additional commentary or explanations.
Each concept should include:
1. A main visual element or scene
2. Text placement and content
3. Color scheme suggestions
4. Emotional trigger or hook
5. Brief explanation of why this concept would be effective"""
# Build the prompt
prompt = f"""
**Instructions:**
Please generate {num_concepts} thumbnail concept ideas for a YouTube video with the following information:
**Video Title:** {video_title}
**Video Description:** {video_description}
**Target Audience:** {target_audience}
**Content Type:** {content_type}
**Style Preference:** {style_preference}
**Specific Instructions:**
* Each concept should be clearly separated and numbered.
* Focus on creating thumbnails that stand out in search results and recommendations.
* Consider the target audience's interests and preferences.
* Include specific details about visual elements, text placement, and color schemes.
* Explain why each concept would be effective for this specific video.
"""
try:
logger.info("Sending request to LLM for thumbnail concepts")
response = llm_text_gen(prompt, system_prompt=system_prompt)
logger.info(f"Received response from LLM: {len(response)} characters")
return response
except Exception as err:
logger.error(f"Error generating thumbnail concepts: {err}")
logger.error(traceback.format_exc())
st.error(f"Error: Failed to generate thumbnail concepts: {err}")
return None
def generate_thumbnail_design(concept_description, style_preference, aspect_ratio="16:9", keywords=None, style=None, focus=None):
"""Generate a thumbnail image based on the concept description."""
logger.info(f"Generating thumbnail design for concept: '{concept_description[:50]}...'")
logger.info(f"Parameters: style_preference={style_preference}, aspect_ratio={aspect_ratio}, keywords={keywords}, style={style}, focus={focus}")
# Create a prompt for the image generation
image_prompt = f"""
Create a YouTube thumbnail image with the following specifications:
Concept: {concept_description}
Style: {style_preference}
Aspect Ratio: {aspect_ratio}
The image should be:
- High contrast and visually striking
- Suitable for a YouTube thumbnail
- Include the specified visual elements and text
- Follow the color scheme described
- Optimized for small display sizes
Make sure the text is large and readable, and the main subject is centered and prominent.
"""
try:
logger.info("Sending request to Gemini for thumbnail image")
# Generate the image using Gemini with enhanced prompt
img_path = generate_gemini_image(
image_prompt,
keywords=keywords,
style=style,
focus=focus,
enhance_prompt=True
)
logger.info(f"Received image from Gemini: {img_path}")
return img_path
except Exception as err:
logger.error(f"Error generating thumbnail image: {err}")
logger.error(traceback.format_exc())
st.error(f"Error: Failed to generate thumbnail image: {err}")
return None
def edit_thumbnail_image(img_path, edit_instructions):
"""Edit a thumbnail image based on user instructions."""
logger.info(f"Editing thumbnail image: '{img_path}'")
logger.info(f"Edit instructions: '{edit_instructions}'")
try:
logger.info("Sending request to Gemini for image editing")
# Edit the image using Gemini
edited_img_path = edit_image(img_path, edit_instructions)
logger.info(f"Image editing completed. Edited image path: {edited_img_path}")
# Return the path to the edited image
return edited_img_path
except Exception as err:
logger.error(f"Error editing thumbnail image: {err}")
logger.error(traceback.format_exc())
st.error(f"Error: Failed to edit thumbnail image: {err}")
return None
def analyze_thumbnail(thumbnail_path):
"""Analyze a thumbnail for effectiveness."""
logger.info(f"Analyzing thumbnail: '{thumbnail_path}'")
# This would typically involve image analysis, but for now we'll use AI to provide feedback
system_prompt = """You are a YouTube thumbnail expert specializing in analyzing and providing feedback on thumbnail designs.
Your task is to analyze the thumbnail and provide constructive feedback on its effectiveness.
Focus on aspects like visual hierarchy, text readability, emotional impact, and click-worthiness."""
# For now, we'll just return a placeholder analysis
# In a real implementation, we would analyze the actual image
logger.info("Generating thumbnail analysis")
return """
**Thumbnail Analysis:**
- **Visual Hierarchy:** The main subject is well-positioned and stands out against the background.
- **Text Readability:** The text is clear and readable, with good contrast against the background.
- **Emotional Impact:** The thumbnail creates curiosity and emotional connection with the target audience.
- **Click-worthiness:** The design is likely to attract clicks due to its visual appeal and clear value proposition.
**Suggestions for Improvement:**
- Consider adding a subtle border to make the thumbnail stand out more in search results.
- The text could be slightly larger for better readability on mobile devices.
- Adding a small icon or logo could help with brand recognition.
"""
def parse_concepts(concepts_text):
"""Parse the concepts text into a list of individual concepts."""
logger.info("Parsing concepts text into individual concepts")
concept_list = []
current_concept = ""
for line in concepts_text.split('\n'):
if line.strip().startswith(('1.', '2.', '3.', '4.', '5.')):
if current_concept:
concept_list.append(current_concept.strip())
current_concept = line
else:
current_concept += "\n" + line
if current_concept:
concept_list.append(current_concept.strip())
logger.info(f"Parsed {len(concept_list)} concepts from the response")
return concept_list
def write_yt_thumbnail():
"""Create a user interface for YouTube Thumbnail Generator."""
logger.info("Initializing YouTube Thumbnail Generator UI")
st.title("YouTube Thumbnail Generator")
st.write("Create engaging, click-worthy thumbnails for your YouTube videos.")
# Initialize session state for generated thumbnails if it doesn't exist
if "generated_thumbnails" not in st.session_state:
st.session_state.generated_thumbnails = []
if "thumbnail_concepts" not in st.session_state:
st.session_state.thumbnail_concepts = None
if "current_thumbnail_path" not in st.session_state:
st.session_state.current_thumbnail_path = None
if "concept_list" not in st.session_state:
st.session_state.concept_list = []
if "editing_thumbnail" not in st.session_state:
st.session_state.editing_thumbnail = False
if "edit_instructions" not in st.session_state:
st.session_state.edit_instructions = ""
if "edited_thumbnail_path" not in st.session_state:
st.session_state.edited_thumbnail_path = None
if "show_edit_form" not in st.session_state:
st.session_state.show_edit_form = False
# Create tabs for different sections
tab1, tab2 = st.tabs(["Basic Info", "Style & Generation"])
with tab1:
# Basic information inputs
video_title = st.text_input("Video Title",
placeholder="e.g., 10 Tips for Better Photography")
video_description = st.text_area("Video Description",
placeholder="Brief description of your video content")
target_audience = st.text_input("Target Audience",
placeholder="e.g., photography enthusiasts, beginners")
# Content type selection
content_type = st.selectbox("Content Type", [
"Tutorial/How-to",
"Vlog",
"Review",
"Educational",
"Entertainment",
"News/Update",
"Product Showcase",
"Challenge",
"Reaction",
"Comparison"
])
with tab2:
# Style preferences
st.subheader("Style Preferences")
# Create columns for style options
col1, col2 = st.columns(2)
with col1:
style_preference = st.selectbox("Thumbnail Style", [
"Bold and Dramatic",
"Clean and Minimal",
"Colorful and Vibrant",
"Dark and Moody",
"Professional and Corporate",
"Playful and Fun",
"Retro/Vintage",
"Modern and Sleek"
])
num_concepts = st.slider("Number of Concepts", 1, 5, 3)
with col2:
aspect_ratio = st.selectbox("Aspect Ratio", [
"16:9 (Standard)",
"1:1 (Square)",
"4:3 (Classic)",
"9:16 (Vertical)"
])
include_text = st.checkbox("Include Text Overlay", value=True)
if include_text:
text_style = st.selectbox("Text Style", [
"Bold and Impactful",
"Clean and Readable",
"Stylized and Thematic",
"Minimal and Subtle"
])
# Advanced AI Prompt Settings
st.subheader("Advanced AI Prompt Settings")
# Create columns for advanced settings
col3, col4 = st.columns(2)
with col3:
# Image style selection
image_style = st.selectbox("Image Style", [
"Auto (AI will choose best style)",
"Photorealistic",
"Artistic",
"Cartoon/Anime",
"Sketch/Drawing",
"Digital Art",
"3D Render"
])
# Extract style for the generate_gemini_image function
style = None
if image_style == "Photorealistic":
style = "photorealistic"
elif image_style == "Artistic":
style = "artistic"
elif image_style == "Cartoon/Anime":
style = "cartoon"
elif image_style == "Sketch/Drawing":
style = "sketch"
elif image_style == "Digital Art":
style = "digital_art"
elif image_style == "3D Render":
style = "3d_render"
with col4:
# Focus selection for photorealistic images
focus = None
if style == "photorealistic":
focus = st.selectbox("Image Focus", [
"Auto (AI will choose best focus)",
"Portraits",
"Objects",
"Motion",
"Wide-angle"
])
# Extract focus for the generate_gemini_image function
if focus == "Portraits":
focus = "portraits"
elif focus == "Objects":
focus = "objects"
elif focus == "Motion":
focus = "motion"
elif focus == "Wide-angle":
focus = "wide-angle"
elif focus == "Auto (AI will choose best focus)":
focus = None
# Keywords for enhanced prompt generation
st.subheader("Keywords for Enhanced Prompt")
st.write("Add keywords to enhance the AI prompt generation. These will help create more detailed and accurate thumbnails.")
# Create a text area for keywords
keywords_input = st.text_area(
"Keywords (comma-separated)",
placeholder="e.g., vibrant, energetic, bold, eye-catching, professional"
)
# Process keywords
keywords = None
if keywords_input:
keywords = [k.strip() for k in keywords_input.split(",") if k.strip()]
logger.info(f"User provided keywords: {keywords}")
# Generate button
if st.button("Generate Thumbnail Concepts"):
if not video_title:
st.error("Please enter a video title.")
return
with st.spinner("Generating thumbnail concepts..."):
logger.info("User clicked Generate Thumbnail Concepts button")
concepts = generate_thumbnail_concepts(
video_title,
video_description,
target_audience,
content_type,
style_preference,
num_concepts
)
if concepts:
# Store the concepts in session state
st.session_state.thumbnail_concepts = concepts
# Parse the concepts and store in session state
st.session_state.concept_list = parse_concepts(concepts)
logger.info("Stored thumbnail concepts in session state")
# Display the concepts in tabs
st.subheader("Thumbnail Concepts")
# Create tabs for each concept
concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))])
for i, tab in enumerate(concept_tabs):
with tab:
st.markdown(st.session_state.concept_list[i])
# Add a button to generate image for this concept
if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_{i}"):
with st.spinner(f"Generating thumbnail image for concept {i+1}..."):
logger.info(f"User selected concept {i+1} for image generation")
# Get the selected concept
selected_concept = st.session_state.concept_list[i]
# Generate the thumbnail image with enhanced prompt
img_path = generate_thumbnail_design(
selected_concept,
style_preference,
aspect_ratio.split()[0], # Extract just the ratio part
keywords=keywords,
style=style,
focus=focus
)
if img_path:
# Store the current thumbnail path in session state
st.session_state.current_thumbnail_path = img_path
logger.info(f"Stored current thumbnail path in session state: {img_path}")
# Display the generated image
st.subheader("Generated Thumbnail")
st.image(img_path, use_container_width=True)
# Add download button
with open(img_path, "rb") as file:
st.download_button(
label="Download Thumbnail",
data=file,
file_name=f"youtube_thumbnail_{int(time.time())}.png",
mime="image/png"
)
# Add image editing section
st.subheader("Edit Thumbnail")
st.write("Make changes to your thumbnail by providing instructions below:")
# Create a text area for edit instructions
edit_instructions = st.text_area(
"Edit Instructions",
placeholder="e.g., Make the background darker, Add a red border, Change the text color to white",
key=f"edit_instructions_{i}"
)
# Store edit instructions in session state
st.session_state.edit_instructions = edit_instructions
# Add a button to apply edits
if st.button("Apply Edits", key=f"apply_edits_{i}"):
if not edit_instructions:
st.warning("Please provide edit instructions.")
else:
# Set editing flag
st.session_state.editing_thumbnail = True
st.session_state.show_edit_form = True
# Rerun to update the UI
st.rerun()
# Add analysis button
if st.button("Analyze Thumbnail", key=f"analyze_{i}"):
logger.info("User clicked Analyze Thumbnail button")
analysis = analyze_thumbnail(img_path)
st.subheader("Thumbnail Analysis")
st.markdown(analysis)
else:
st.error("Failed to generate thumbnail concepts. Please try again.")
# Display previously generated concepts if they exist in session state
elif st.session_state.thumbnail_concepts and st.session_state.concept_list:
logger.info("Displaying previously generated concepts from session state")
st.subheader("Thumbnail Concepts")
# Create tabs for each concept
concept_tabs = st.tabs([f"Concept {i+1}" for i in range(len(st.session_state.concept_list))])
for i, tab in enumerate(concept_tabs):
with tab:
st.markdown(st.session_state.concept_list[i])
# Add a button to generate image for this concept
if st.button(f"Generate Image for Concept {i+1}", key=f"gen_img_existing_{i}"):
with st.spinner(f"Generating thumbnail image for concept {i+1}..."):
logger.info(f"User selected concept {i+1} for image generation")
# Get the selected concept
selected_concept = st.session_state.concept_list[i]
# Generate the thumbnail image with enhanced prompt
img_path = generate_thumbnail_design(
selected_concept,
style_preference,
aspect_ratio.split()[0], # Extract just the ratio part
keywords=keywords,
style=style,
focus=focus
)
if img_path:
# Store the current thumbnail path in session state
st.session_state.current_thumbnail_path = img_path
logger.info(f"Stored current thumbnail path in session state: {img_path}")
# Display the generated image
st.subheader("Generated Thumbnail")
st.image(img_path, use_container_width=True)
# Add download button
with open(img_path, "rb") as file:
st.download_button(
label="Download Thumbnail",
data=file,
file_name=f"youtube_thumbnail_{int(time.time())}.png",
mime="image/png"
)
# Add image editing section
st.subheader("Edit Thumbnail")
st.write("Make changes to your thumbnail by providing instructions below:")
# Create a text area for edit instructions
edit_instructions = st.text_area(
"Edit Instructions",
placeholder="e.g., Make the background darker, Add a red border, Change the text color to white",
key=f"edit_instructions_existing_{i}"
)
# Store edit instructions in session state
st.session_state.edit_instructions = edit_instructions
# Add a button to apply edits
if st.button("Apply Edits", key=f"apply_edits_existing_{i}"):
if not edit_instructions:
st.warning("Please provide edit instructions.")
else:
# Set editing flag
st.session_state.editing_thumbnail = True
st.session_state.show_edit_form = True
# Rerun to update the UI
st.rerun()
# Add analysis button
if st.button("Analyze Thumbnail", key=f"analyze_existing_{i}"):
logger.info("User clicked Analyze Thumbnail button")
analysis = analyze_thumbnail(img_path)
st.subheader("Thumbnail Analysis")
st.markdown(analysis)
# Display current thumbnail if it exists in session state
elif st.session_state.current_thumbnail_path:
logger.info(f"Displaying current thumbnail from session state: {st.session_state.current_thumbnail_path}")
st.subheader("Current Thumbnail")
st.image(st.session_state.current_thumbnail_path, use_container_width=True)
# Add download button
with open(st.session_state.current_thumbnail_path, "rb") as file:
st.download_button(
label="Download Thumbnail",
data=file,
file_name=f"youtube_thumbnail_{int(time.time())}.png",
mime="image/png"
)
# Add image editing section
st.subheader("Edit Thumbnail")
st.write("Make changes to your thumbnail by providing instructions below:")
# Create a text area for edit instructions
edit_instructions = st.text_area(
"Edit Instructions",
placeholder="e.g., Make the background darker, Add a red border, Change the text color to white",
key="edit_instructions_current",
value=st.session_state.edit_instructions if st.session_state.edit_instructions else ""
)
# Store edit instructions in session state
st.session_state.edit_instructions = edit_instructions
# Add a button to apply edits
if st.button("Apply Edits", key="apply_edits_current"):
if not edit_instructions:
st.warning("Please provide edit instructions.")
else:
# Set editing flag
st.session_state.editing_thumbnail = True
st.session_state.show_edit_form = True
# Rerun to update the UI
st.rerun()
# Add analysis button
if st.button("Analyze Thumbnail", key="analyze_current"):
logger.info("User clicked Analyze Thumbnail button")
analysis = analyze_thumbnail(st.session_state.current_thumbnail_path)
st.subheader("Thumbnail Analysis")
st.markdown(analysis)
# Handle the editing process
if st.session_state.editing_thumbnail and st.session_state.show_edit_form:
st.subheader("Editing Thumbnail")
# Show a spinner while editing
with st.spinner("Editing thumbnail..."):
logger.info(f"User provided edit instructions: '{st.session_state.edit_instructions}'")
# Edit the thumbnail image
edited_img_path = edit_thumbnail_image(st.session_state.current_thumbnail_path, st.session_state.edit_instructions)
if edited_img_path:
# Update the current thumbnail path in session state
st.session_state.edited_thumbnail_path = edited_img_path
logger.info(f"Updated current thumbnail path in session state: {edited_img_path}")
# Reset editing flags
st.session_state.editing_thumbnail = False
st.session_state.show_edit_form = False
# Display the edited image
st.subheader("Edited Thumbnail")
st.image(edited_img_path, use_container_width=True)
# Add download button for the edited image
with open(edited_img_path, "rb") as file:
st.download_button(
label="Download Edited Thumbnail",
data=file,
file_name=f"youtube_thumbnail_edited_{int(time.time())}.png",
mime="image/png"
)
# Update the current thumbnail path to the edited one
st.session_state.current_thumbnail_path = edited_img_path
# Add a button to continue editing
if st.button("Continue Editing"):
st.session_state.show_edit_form = True
st.rerun()
else:
# Reset editing flags
st.session_state.editing_thumbnail = False
st.session_state.show_edit_form = False
st.error("Failed to edit the thumbnail. Please try again with different instructions.")

View File

@@ -0,0 +1,452 @@
"""
YouTube Title Generator Module
This module provides functionality for generating YouTube video titles.
"""
import streamlit as st
import time
import logging
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('youtube_title_generator')
def analyze_title(title):
"""Analyze a YouTube title for SEO and clickbait."""
logger.info(f"Analyzing title: '{title}'")
# Character count
char_count = len(title)
optimal_length = 50 <= char_count <= 60
logger.info(f"Character count: {char_count}, Optimal length: {optimal_length}")
# Clickbait detection. TBD: Use AI to detect clickbait.
clickbait_phrases = [
"shocking", "you won't believe", "gone wrong", "gone sexual",
"free v-bucks", "free robux", "100%", "gone viral", "viral",
"you need to see this", "wait till the end", "at 3am", "3am",
"don't watch this", "watch till the end", "gone too far",
"insane", "unbelievable", "mind-blowing", "life-changing",
"secret", "hidden", "revealed", "exposed", "leaked",
"never before seen", "first time ever", "world's first",
"no one knows", "experts hate this", "doctors hate this",
"this will change your life", "this will blow your mind",
"you've been doing it wrong", "the truth about", "the real reason",
"what they don't want you to know", "what they're hiding",
"what they don't tell you", "what you need to know",
"what you should know", "what you must know", "what you must see",
"what you must watch", "what you must do", "what you must have",
"what you must buy", "what you must try", "what you must avoid",
"what you must stop doing", "what you must start doing",
"what you must change", "what you must learn", "what you must understand",
"what you must realize", "what you must accept", "what you must believe",
"what you must know about", "what you must see about", "what you must watch about",
"what you must do about", "what you must have about", "what you must buy about",
"what you must try about", "what you must avoid about", "what you must stop doing about",
"what you must start doing about", "what you must change about", "what you must learn about",
"what you must understand about", "what you must realize about", "what you must accept about",
"what you must believe about", "what you must know about", "what you must see about",
"what you must watch about", "what you must do about", "what you must have about",
"what you must buy about", "what you must try about", "what you must avoid about",
"what you must stop doing about", "what you must start doing about", "what you must change about",
"what you must learn about", "what you must understand about", "what you must realize about",
"what you must accept about", "what you must believe about"
]
clickbait_score = 0
detected_phrases = []
for phrase in clickbait_phrases:
if phrase.lower() in title.lower():
clickbait_score += 1
detected_phrases.append(phrase)
is_clickbait = clickbait_score > 0
logger.info(f"Clickbait detection: score={clickbait_score}, is_clickbait={is_clickbait}")
if detected_phrases:
logger.info(f"Detected clickbait phrases: {', '.join(detected_phrases)}")
# SEO elements
has_number = any(char.isdigit() for char in title)
has_question = "?" in title
has_colon = ":" in title
has_brackets = "[" in title or "]" in title or "(" in title or ")" in title
logger.info(f"SEO elements: has_number={has_number}, has_question={has_question}, has_colon={has_colon}, has_brackets={has_brackets}")
# Calculate SEO score
seo_score = 0
if optimal_length:
seo_score += 3
if has_number:
seo_score += 1
if has_question:
seo_score += 1
if has_colon:
seo_score += 1
if has_brackets:
seo_score += 1
if not is_clickbait:
seo_score += 2
logger.info(f"Final SEO score: {seo_score}/10")
return {
"char_count": char_count,
"optimal_length": optimal_length,
"is_clickbait": is_clickbait,
"clickbait_score": clickbait_score,
"seo_score": seo_score,
"has_number": has_number,
"has_question": has_question,
"has_colon": has_colon,
"has_brackets": has_brackets
}
def generate_youtube_title(target_audience, main_points, tone_style, use_case, num_titles=5, progress_bar=None):
""" Generate youtube title generator """
logger.info(f"Starting title generation with parameters: target_audience='{target_audience}', main_points='{main_points}', tone_style='{tone_style}', use_case='{use_case}', num_titles={num_titles}")
# Create a custom system prompt that doesn't include blog-specific instructions
system_prompt = """You are a YouTube title expert specializing in creating engaging, clickable video titles.
Your task is to generate YouTube video titles based on the provided information.
Focus ONLY on creating titles that are optimized for YouTube.
Return ONLY the titles, one per line, without any numbering or additional text."""
prompt = f"""
**Instructions:**
Please generate {num_titles} YouTube title options for a video about **{main_points}** based on the following information:
**Target Audience:** {target_audience}
**Tone and Style:** {tone_style}
**Use Case:** {use_case}
**Specific Instructions:**
* Make the titles catchy and attention-grabbing.
* Use relevant keywords to improve SEO.
* Tailor the language and tone to the target audience.
* Ensure the title reflects the content and use case of the video.
* Return ONLY the titles, one per line, without any numbering or additional text.
"""
logger.info("Generated prompt for title generation")
logger.debug(f"Prompt: {prompt}")
logger.debug(f"System prompt: {system_prompt}")
try:
# Update progress bar if provided
if progress_bar:
progress_bar.progress(30)
progress_bar.text("Analyzing your content and target audience...")
logger.info("Progress bar updated: 30% - Analyzing content and target audience")
# Simulate some processing time to show progress
time.sleep(1)
if progress_bar:
progress_bar.progress(60)
progress_bar.text("Generating creative title options...")
logger.info("Progress bar updated: 60% - Generating creative title options")
# Get the response from the language model with custom system prompt
logger.info("Calling LLM for title generation with custom system prompt")
start_time = time.time()
response = llm_text_gen(prompt, system_prompt=system_prompt)
end_time = time.time()
logger.info(f"LLM response received in {end_time - start_time:.2f} seconds")
logger.debug(f"Raw LLM response: {response}")
if progress_bar:
progress_bar.progress(90)
progress_bar.text("Processing and formatting titles...")
logger.info("Progress bar updated: 90% - Processing and formatting titles")
# Split the response into individual titles
titles = [title.strip() for title in response.split('\n') if title.strip()]
logger.info(f"Generated {len(titles)} titles")
for i, title in enumerate(titles, 1):
logger.info(f"Title {i}: '{title}'")
if progress_bar:
progress_bar.progress(100)
progress_bar.text("Titles generated successfully!")
logger.info("Progress bar updated: 100% - Titles generated successfully")
return titles
except Exception as err:
logger.error(f"Error generating titles: {err}", exc_info=True)
if progress_bar:
progress_bar.progress(100)
progress_bar.text("Error generating titles. Please try again.")
logger.info("Progress bar updated: 100% - Error generating titles")
st.error(f"Error: Failed to get response from LLM: {err}")
return None
def write_yt_title():
"""Create a user interface for YouTube Title Generator."""
logger.info("Initializing YouTube Title Generator UI")
st.write("Generate engaging YouTube video titles that drive clicks and views.")
# Initialize session state for generated titles if it doesn't exist
if "generated_titles" not in st.session_state:
st.session_state.generated_titles = None
# Main points input (full width)
main_points = st.text_area("Main Points/Keywords (comma-separated)",
placeholder="e.g., cooking tips, healthy recipes, quick meals")
# Create columns for the other inputs
col1, col2, col3, col4 = st.columns(4)
with col1:
tone_style = st.selectbox("Tone/Style",
["Professional", "Casual", "Humorous", "Educational", "Entertaining", "Inspirational"])
with col2:
target_audience = st.text_input("Target Audience",
placeholder="e.g., beginners, professionals, parents")
with col3:
use_case = st.selectbox("Use Case",
["How-to/Tutorial", "Vlog", "Review", "Educational", "Entertainment", "News"])
with col4:
num_titles = st.number_input("Number of Titles",
min_value=1,
max_value=20,
value=5,
step=1)
if st.button("Generate Titles"):
logger.info("Generate Titles button clicked")
logger.info(f"User inputs: main_points='{main_points}', tone_style='{tone_style}', target_audience='{target_audience}', use_case='{use_case}', num_titles={num_titles}")
if not main_points:
logger.warning("No main points provided")
st.error("Please enter main points/keywords.")
return
# Create a progress bar
progress_bar = st.progress(0)
progress_bar.text("Initializing title generation...")
logger.info("Created progress bar for title generation")
# Generate titles with progress updates
logger.info("Calling generate_youtube_title function")
titles = generate_youtube_title(main_points, tone_style, target_audience, use_case, num_titles, progress_bar)
# Clear the progress bar after a short delay
time.sleep(1)
progress_bar.empty()
logger.info("Cleared progress bar")
if titles:
logger.info(f"Successfully generated {len(titles)} titles")
# Store titles in session state for persistence
st.session_state.generated_titles = titles
# Display titles section
st.markdown("""
<div style='background-color: #f0f2f6; padding: 20px; border-radius: 10px; margin-bottom: 20px;'>
<h2 style='color: #FF0000; text-align: center;'>Generated YouTube Titles</h2>
<p style='text-align: center;'>Click on a title to see detailed analysis and copy options</p>
</div>
""", unsafe_allow_html=True)
# Display titles with analysis
for i, title in enumerate(titles, 1):
logger.info(f"Analyzing title {i}: '{title}'")
# Create a more visually appealing expander
with st.expander(f"Title {i}: {title}", expanded=False):
# Add a divider for better visual separation
st.markdown("---")
# Title display with better formatting
st.markdown(f"""
<div style='background-color: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 5px solid #FF0000;'>
<h3 style='margin: 0;'>{title}</h3>
</div>
""", unsafe_allow_html=True)
# Analysis section
st.markdown("### Analysis")
analysis = analyze_title(title)
# Create columns for analysis metrics
col1, col2 = st.columns(2)
with col1:
# Character count
st.markdown("#### Character Count")
st.write(f"**{analysis['char_count']}** characters")
if analysis['optimal_length']:
st.success("✅ Optimal length (50-60 characters)")
else:
st.warning("⚠️ Not optimal length (should be 50-60 characters)")
# Clickbait detection
st.markdown("#### Clickbait Detection")
if analysis['is_clickbait']:
st.error(f"⚠️ Possible clickbait detected (score: {analysis['clickbait_score']})")
else:
st.success("✅ No clickbait detected")
with col2:
# SEO score
st.markdown("#### SEO Score")
score_color = "#28a745" if analysis['seo_score'] >= 7 else "#ffc107" if analysis['seo_score'] >= 5 else "#dc3545"
st.markdown(f"<h2 style='color: {score_color};'>{analysis['seo_score']}/10</h2>", unsafe_allow_html=True)
if analysis['seo_score'] >= 7:
st.success("✅ Good SEO score")
elif analysis['seo_score'] >= 5:
st.warning("⚠️ Moderate SEO score")
else:
st.error("❌ Low SEO score")
# SEO elements
st.markdown("#### SEO Elements")
elements = []
if analysis['has_number']:
elements.append("✅ Contains numbers")
if analysis['has_question']:
elements.append("✅ Contains question mark")
if analysis['has_colon']:
elements.append("✅ Contains colon")
if analysis['has_brackets']:
elements.append("✅ Contains brackets/parentheses")
for element in elements:
st.write(element)
# Copy functionality using session state
st.markdown("### Copy Title")
st.code(title, language="text")
# Use a different approach for copy functionality
copy_key = f"copy_{i}"
if st.button(f"Copy Title {i}", key=copy_key):
# Use JavaScript to copy to clipboard
escaped_title = title.replace('"', '\\"')
st.markdown(
f"""
<script>
navigator.clipboard.writeText("{escaped_title}");
</script>
""",
unsafe_allow_html=True
)
st.success(f"✅ Title {i} copied to clipboard!")
else:
logger.error("Failed to generate titles")
st.error("Failed to generate titles. Please try again.")
# Display previously generated titles if they exist in session state
elif st.session_state.generated_titles:
titles = st.session_state.generated_titles
# Display titles section
st.markdown("""
<div style='background-color: #f0f2f6; padding: 20px; border-radius: 10px; margin-bottom: 20px;'>
<h2 style='color: #FF0000; text-align: center;'>Generated YouTube Titles</h2>
<p style='text-align: center;'>Click on a title to see detailed analysis and copy options</p>
</div>
""", unsafe_allow_html=True)
# Display titles with analysis
for i, title in enumerate(titles, 1):
logger.info(f"Analyzing title {i}: '{title}'")
# Create a more visually appealing expander
with st.expander(f"Title {i}: {title}", expanded=False):
# Add a divider for better visual separation
st.markdown("---")
# Title display with better formatting
st.markdown(f"""
<div style='background-color: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 5px solid #FF0000;'>
<h3 style='margin: 0;'>{title}</h3>
</div>
""", unsafe_allow_html=True)
# Analysis section
st.markdown("### Analysis")
analysis = analyze_title(title)
# Create columns for analysis metrics
col1, col2 = st.columns(2)
with col1:
# Character count
st.markdown("#### Character Count")
st.write(f"**{analysis['char_count']}** characters")
if analysis['optimal_length']:
st.success("✅ Optimal length (50-60 characters)")
else:
st.warning("⚠️ Not optimal length (should be 50-60 characters)")
# Clickbait detection
st.markdown("#### Clickbait Detection")
if analysis['is_clickbait']:
st.error(f"⚠️ Possible clickbait detected (score: {analysis['clickbait_score']})")
else:
st.success("✅ No clickbait detected")
with col2:
# SEO score
st.markdown("#### SEO Score")
score_color = "#28a745" if analysis['seo_score'] >= 7 else "#ffc107" if analysis['seo_score'] >= 5 else "#dc3545"
st.markdown(f"<h2 style='color: {score_color};'>{analysis['seo_score']}/10</h2>", unsafe_allow_html=True)
if analysis['seo_score'] >= 7:
st.success("✅ Good SEO score")
elif analysis['seo_score'] >= 5:
st.warning("⚠️ Moderate SEO score")
else:
st.error("❌ Low SEO score")
# SEO elements
st.markdown("#### SEO Elements")
elements = []
if analysis['has_number']:
elements.append("✅ Contains numbers")
if analysis['has_question']:
elements.append("✅ Contains question mark")
if analysis['has_colon']:
elements.append("✅ Contains colon")
if analysis['has_brackets']:
elements.append("✅ Contains brackets/parentheses")
for element in elements:
st.write(element)
# Copy functionality using session state
st.markdown("### Copy Title")
st.code(title, language="text")
# Use a different approach for copy functionality
copy_key = f"copy_{i}"
if st.button(f"Copy Title {i}", key=copy_key):
# Use JavaScript to copy to clipboard
escaped_title = title.replace('"', '\\"')
st.markdown(
f"""
<script>
navigator.clipboard.writeText("{escaped_title}");
</script>
""",
unsafe_allow_html=True
)
st.success(f"✅ Title {i} copied to clipboard!")