Youtube AI Writer Tools
This commit is contained in:
@@ -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.*
|
||||
108
lib/ai_writers/youtube_writers/modules/README_endScreen.md
Normal file
108
lib/ai_writers/youtube_writers/modules/README_endScreen.md
Normal 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?
|
||||
5
lib/ai_writers/youtube_writers/modules/__init__.py
Normal file
5
lib/ai_writers/youtube_writers/modules/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
YouTube AI Writer Modules
|
||||
|
||||
This package contains modular components for the YouTube AI Writer functionality.
|
||||
"""
|
||||
404
lib/ai_writers/youtube_writers/modules/description_generator.py
Normal file
404
lib/ai_writers/youtube_writers/modules/description_generator.py
Normal 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
|
||||
740
lib/ai_writers/youtube_writers/modules/end_screen_generator.py
Normal file
740
lib/ai_writers/youtube_writers/modules/end_screen_generator.py
Normal 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.")
|
||||
556
lib/ai_writers/youtube_writers/modules/script_generator.py
Normal file
556
lib/ai_writers/youtube_writers/modules/script_generator.py
Normal 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.")
|
||||
622
lib/ai_writers/youtube_writers/modules/thumbnail_generator.py
Normal file
622
lib/ai_writers/youtube_writers/modules/thumbnail_generator.py
Normal 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.")
|
||||
452
lib/ai_writers/youtube_writers/modules/title_generator.py
Normal file
452
lib/ai_writers/youtube_writers/modules/title_generator.py
Normal 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!")
|
||||
Reference in New Issue
Block a user