From 8312dbaaac78abb27f0bc4818ba931548fa3548a Mon Sep 17 00:00:00 2001 From: ajaysi Date: Tue, 8 Apr 2025 18:37:35 +0530 Subject: [PATCH] Youtube AI Writer Tools --- alwrity.py | 1 - lib/ai_writers/ai_copywriter/README.md | 141 ++++ .../ai_copywriter/{README => README_TBD.md} | 0 .../ai_copywriter/copywriter_dashboard.py | 4 +- lib/ai_writers/youtube_ai_writer.py | 257 ------ lib/ai_writers/youtube_writers/README | 225 ++++++ .../modules/README_Thumbnail_Generator.md | 96 +++ .../modules/README_endScreen.md | 108 +++ .../youtube_writers/modules/__init__.py | 5 + .../modules/description_generator.py | 404 ++++++++++ .../modules/end_screen_generator.py | 740 ++++++++++++++++++ .../modules/script_generator.py | 556 +++++++++++++ .../modules/thumbnail_generator.py | 622 +++++++++++++++ .../modules/title_generator.py | 452 +++++++++++ .../youtube_writers/youtube_ai_writer.py | 242 ++++++ .../gen_gemini_images.py | 377 +++++++++ .../main_generate_image_from_prompt.py | 7 +- lib/utils/alwrity_utils.py | 16 +- 18 files changed, 3979 insertions(+), 274 deletions(-) create mode 100644 lib/ai_writers/ai_copywriter/README.md rename lib/ai_writers/ai_copywriter/{README => README_TBD.md} (100%) delete mode 100644 lib/ai_writers/youtube_ai_writer.py create mode 100644 lib/ai_writers/youtube_writers/README create mode 100644 lib/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md create mode 100644 lib/ai_writers/youtube_writers/modules/README_endScreen.md create mode 100644 lib/ai_writers/youtube_writers/modules/__init__.py create mode 100644 lib/ai_writers/youtube_writers/modules/description_generator.py create mode 100644 lib/ai_writers/youtube_writers/modules/end_screen_generator.py create mode 100644 lib/ai_writers/youtube_writers/modules/script_generator.py create mode 100644 lib/ai_writers/youtube_writers/modules/thumbnail_generator.py create mode 100644 lib/ai_writers/youtube_writers/modules/title_generator.py create mode 100644 lib/ai_writers/youtube_writers/youtube_ai_writer.py create mode 100644 lib/gpt_providers/text_to_image_generation/gen_gemini_images.py diff --git a/alwrity.py b/alwrity.py index f500b9d8..40c25a7c 100644 --- a/alwrity.py +++ b/alwrity.py @@ -67,7 +67,6 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -from lib.utils.ui_setup import setup_ui from lib.utils.api_key_manager.api_key_manager import APIKeyManager, render from lib.utils.api_key_manager.validation import check_all_api_keys from dotenv import load_dotenv diff --git a/lib/ai_writers/ai_copywriter/README.md b/lib/ai_writers/ai_copywriter/README.md new file mode 100644 index 00000000..3a7a9bef --- /dev/null +++ b/lib/ai_writers/ai_copywriter/README.md @@ -0,0 +1,141 @@ +# AI Copywriting Tools + +A comprehensive collection of AI-powered copywriting tools designed to help create compelling, conversion-focused content using various proven frameworks and approaches. + +## Available Copywriting Tools + +### 1. AIDA Copywriter +The AIDA (Attention-Interest-Desire-Action) framework is a classic copywriting approach that guides your audience through a complete journey: +- **Attention**: Captures attention with compelling headlines +- **Interest**: Generates interest through benefits and pain points +- **Desire**: Creates desire by showcasing solutions +- **Action**: Prompts specific actions with strong CTAs + +Best for: Landing pages, sales pages, email campaigns, and direct response advertising. + +### 2. 4C Copywriter +The 4C framework ensures your message is effective and persuasive through: +- **Clear**: Easy to understand messaging +- **Concise**: Brief and to-the-point content +- **Credible**: Evidence-backed claims +- **Compelling**: Interesting and persuasive messaging + +Best for: Email marketing, landing pages, social media, and product descriptions. + +### 3. 4R Copywriter +The 4R framework focuses on building relationships with your audience through: +- **Relevance**: Content that matters to your audience +- **Receptivity**: Timing and context optimization +- **Response**: Clear calls to action +- **Return**: Value-driven content + +Best for: Content marketing, blog posts, and relationship-building campaigns. + +### 4. PAS Copywriter +The PAS (Problem-Agitation-Solution) framework addresses customer pain points: +- **Problem**: Identifies the customer's issue +- **Agitation**: Amplifies the problem's impact +- **Solution**: Presents your offering as the answer + +Best for: Problem-solving content, product launches, and service offerings. + +### 5. FAB Copywriter +The FAB (Features-Advantages-Benefits) framework focuses on product value: +- **Features**: Product characteristics +- **Advantages**: How features stand out +- **Benefits**: Customer value proposition + +Best for: Product descriptions, sales pages, and feature highlights. + +### 6. QUEST Copywriter +The QUEST framework creates engaging storytelling: +- **Qualify**: Identify the right audience +- **Understand**: Show empathy +- **Educate**: Provide value +- **Stimulate**: Create desire +- **Transition**: Guide to action + +Best for: Story-based marketing, brand storytelling, and content marketing. + +### 7. STAR Copywriter +The STAR framework focuses on social proof and testimonials: +- **Situation**: Context of the problem +- **Task**: Challenge faced +- **Action**: Solution implemented +- **Result**: Outcome achieved + +Best for: Case studies, testimonials, and success stories. + +### 8. OATH Copywriter +The OATH framework addresses customer objections: +- **Objection**: Identify common concerns +- **Acknowledge**: Show understanding +- **Transform**: Turn negatives to positives +- **Handle**: Provide solutions + +Best for: Sales pages, product launches, and objection handling. + +### 9. AIDPPC Copywriter +The AIDPPC framework extends AIDA with additional elements: +- **Attention**: Initial hook +- **Interest**: Generate curiosity +- **Desire**: Create want +- **Proof**: Provide evidence +- **Push**: Create urgency +- **Close**: Final call to action + +Best for: Long-form sales pages and comprehensive marketing materials. + +### 10. Emotional Copywriter +Focuses on creating emotional connections through: +- Emotional triggers (FOMO, trust, joy, urgency) +- Personal connections +- Pain point addressing +- Trust building +- Community creation + +Best for: Brand storytelling, emotional marketing, and relationship building. + +## Features + +All copywriting tools include: +- User-friendly interface with Streamlit +- Educational content about each framework +- Customizable input parameters +- Multiple language support +- Tone and style options +- Target audience customization +- Brand-specific content generation +- Retry mechanism for reliable API calls + +## Usage + +1. Select your desired copywriting framework +2. Fill in the required information: + - Brand/Company details + - Target audience + - Unique selling points + - Desired tone and style + - Platform-specific requirements +3. Generate your copy +4. Review and refine the output + +## Best Practices + +1. **Know Your Audience**: Always provide detailed target audience information +2. **Be Specific**: Include clear unique selling points and value propositions +3. **Choose the Right Framework**: Match the framework to your content goals +4. **Maintain Consistency**: Keep brand voice and messaging consistent +5. **Test and Optimize**: Use different frameworks for A/B testing +6. **Review and Edit**: Always review AI-generated content for accuracy and tone + +## Technical Requirements + +- Python 3.7+ +- Streamlit +- GPT API access +- Required Python packages (see requirements.txt) + +## Support + +For technical support or questions about specific frameworks, please refer to the documentation or contact the development team. \ No newline at end of file diff --git a/lib/ai_writers/ai_copywriter/README b/lib/ai_writers/ai_copywriter/README_TBD.md similarity index 100% rename from lib/ai_writers/ai_copywriter/README rename to lib/ai_writers/ai_copywriter/README_TBD.md diff --git a/lib/ai_writers/ai_copywriter/copywriter_dashboard.py b/lib/ai_writers/ai_copywriter/copywriter_dashboard.py index 38058f44..24bdf95c 100644 --- a/lib/ai_writers/ai_copywriter/copywriter_dashboard.py +++ b/lib/ai_writers/ai_copywriter/copywriter_dashboard.py @@ -24,8 +24,8 @@ copywriter_modules = [ "aida_copywriter", "pas_copywriter", "fab_copywriter", - "four_c_copywriter", - "four_r_copywriter" + "4c_copywriter", + "4r_copywriter" ] # Dynamically import all copywriter modules diff --git a/lib/ai_writers/youtube_ai_writer.py b/lib/ai_writers/youtube_ai_writer.py deleted file mode 100644 index dccdb3cb..00000000 --- a/lib/ai_writers/youtube_ai_writer.py +++ /dev/null @@ -1,257 +0,0 @@ -import time #Iwish -import os -import json -import streamlit as st - - -def write_yt_description(): - st.title("📽️ YT Description Writer") - col1, col2 = st.columns([1, 1]) - with col1: - keywords = st.text_input('**Describe Your YT video Keywords (comma-separated)**', - help="Enter keywords separated by commas.").split(',') - target_audience = st.multiselect('**Select Your Target Audience**', - ['Beginners', 'Marketers', 'Gamers', 'Foodies', 'Entrepreneurs', 'Students', 'Parents', - 'Tech Enthusiasts', 'General Audience', 'News Readers', 'Finance Enthusiasts'], - help="Select the target audience for your video.") - - with col2: - tone_style = st.selectbox('**Select Tone and Style of YT Description**', - ['Casual', 'Professional', 'Humorous', 'Formal', 'Informal', 'Inspirational'], - help="Select the tone and style of your video.") - language = st.selectbox('**Select YT description Language**', - ['English', 'Spanish', 'Chinese', 'Hindi', 'Arabic'], - help="Select the language for the video description.") - - if st.button('**Generate YT Description**'): - with st.spinner(): - if not keywords: - st.error("🚫 Please provide all required inputs.") - else: - response = generate_youtube_description(keywords, target_audience, tone_style, language) - if response: - st.subheader(f'**🧕👩: Your Final youtube Description !**') - st.write(response) - st.write("\n\n\n\n\n\n") - else: - st.error("💥**Failed to write YT Description. Please try again!**") - - -def generate_youtube_description(keywords, target_audience, tone_style, language): - """ Generate youtube script generator """ - - prompt = f""" - Please write a descriptive YouTube description in {language} for a video about {keywords} based on the following information: - - Keywords: {', '.join(keywords)} - - Target Audience: {', '.join(target_audience)} - - Language for description: {', '.join(language)} - - Tone and Style: {tone_style} - - Specific Instructions: - - - Include Primary Keywords Early: Place the most important keywords at the beginning to enhance SEO. - - Write a Compelling Hook: Start with an engaging sentence to grab attention and entice viewers to watch the video. - - Provide a Brief Overview: Summarize the video's content and what viewers can expect to learn or experience. - - Use Relevant Keywords: Integrate additional keywords naturally to improve searchability. - - Add Timestamps: Include timestamps for different sections of the video, if applicable. - - Include Links: Add links to related videos, playlists, or external resources. - - Encourage Engagement: Ask viewers to like, comment, and subscribe, and include a clear call to action. - - Provide Contact Information: Include relevant social media handles, website links, or contact information. - - Use Clear and Concise Language: Avoid jargon and keep sentences straightforward and easy to understand. - - Include Hashtags: Use relevant hashtags to increase discoverability, placing them at the end of the description. - - Tailor the Language and Tone: Adjust to suit the target audience. - - Engage and Describe: Use descriptive language to make the video sound interesting. - - Be Concise but Informative: Provide enough context about the video. - - Highlight Unique Details: Mention any important details or highlights that make the video unique. - - Ensure Proper Grammar and Spelling: Maintain a high standard of writing. - - Generate a detailed YouTube description that adheres to the above guidelines and includes a compelling hook, a brief overview, relevant keywords, a call to action, hashtags, and any other relevant information. Ensure proper formatting and a clear structure. - """ - - try: - response = generate_text_with_exception_handling(prompt) - return response - except Exception as err: - st.error(f"Exit: Failed to get response from LLM: {err}") - exit(1) - -def write_yt_title(): - """ Generat YT Titles UI """ - st.title("🎬 Write YT Video Titles") - with st.expander("**PRO-TIP** - Read the instructions below.", expanded=True): - col1, col2 = st.columns([5, 5]) - with col1: - main_points = st.text_area('**What is your video about ?**', - placeholder='Write few words on your video for title ? (e.g., "New trek, Latest in news, Finance, Tech...")') - tone_style = st.selectbox('**Select Tone & Style**', ['Casual', 'Professional', 'Humorous', 'Formal', 'Informal', 'Inspirational']) - with col2: - target_audience = st.multiselect('**Select Video Target Audience(One Or Multiple)**', [ - 'Beginners', - 'Marketers', - 'Gamers', - 'Foodies', - 'Entrepreneurs', - 'Students', - 'Parents', - 'Tech Enthusiasts', - 'General Audience', - 'News article', - 'Finance Article']) - - use_case = st.selectbox('**Youtube Title Use Case**', [ - 'Tutorials', - 'Product Reviews', - 'Explainer Videos', - 'Vlogs', - 'Motivational Speeches', - 'Comedy Skits', - 'Educational Content' - ]) - if st.button('**Write YT Titles**'): - with st.status("Assigning AI professional to write your YT Titles..", expanded=True) as status: - if not main_points: - st.error("🚫 Please provide all required inputs.") - else: - response = generate_youtube_title(target_audience, main_points, tone_style, use_case) - if response: - st.subheader(f'**🧕👩: Your Final youtube Titles !**') - st.markdown(response) - st.write("\n\n\n") - else: - st.error("💥**Failed to write Letter. Please try again!**") - - -def generate_youtube_title(target_audience, main_points, tone_style, use_case): - """ Generate youtube script generator """ - - prompt = f""" - **Instructions:** - - Please generate 5 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. - """ - - try: - response = generate_text_with_exception_handling(prompt) - return response - except Exception as err: - st.error(f"Exit: Failed to get response from LLM: {err}") - exit(1) - - -def write_yt_script(): - """ Generate youtube scripts """ - with st.expander("**PRO-TIP** - Read the instructions below.", expanded=True): - col1, col2 = st.columns([5, 5]) - with col1: - main_points = st.text_area('**What is your video about ?**', - placeholder='Write few lines on Video idea for transcript ? (e.g., "New trek, Latest in news, Finance, Tech...")') - tone_style = st.selectbox('**Select Tone & Style**', ['Casual', 'Professional', 'Humorous', 'Formal', 'Informal', 'Inspirational']) - target_audience = st.multiselect('**Select Video Target Audience(One Or Multiple)**', [ - 'Beginners', - 'Marketers', - 'Gamers', - 'Foodies', - 'Entrepreneurs', - 'Students', - 'Parents', - 'Tech Enthusiasts', - 'General Audience', - 'News article', - 'Finance Article' - ]) - with col2: - # Selectbox for Video Length - video_length = st.selectbox('**Select Video Length**', [ - 'Short (1-3 minutes)', - 'Medium (3-5 minutes)', - 'Long (5-10 minutes)', - 'Very Long (10+ minutes)' - ]) - - # Selectbox for Script Structure - script_structure = st.selectbox('**Script Structure**', [ - 'Linear', - 'Storytelling', - 'Q&A' - ]) - - use_case = st.selectbox('**Youtube Script Use Case**', [ - 'Tutorials', - 'Product Reviews', - 'Explainer Videos', - 'Vlogs', - 'Motivational Speeches', - 'Comedy Skits', - 'Educational Content' - ]) - if st.button('**Write YT Script**'): - with st.status("Assigning AI professional to write your YT script..", expanded=True) as status: - if not main_points: - st.error("🚫 Please provide all required inputs.") - else: - response = generate_youtube_script(target_audience, main_points, tone_style, video_length, use_case, script_structure) - if response: - st.subheader(f'**🧕👩: Your Final youtube script!**') - st.write(response) - st.write("\n\n\n\n\n\n") - else: - st.error("💥**Failed to write Letter. Please try again!**") - - -def generate_youtube_script(target_audience, main_points, tone_style, video_length, use_case, script_structure): - """ Generate youtube script generator """ - prompt = f""" - Please write a YouTube script for a video about {main_points} based on the following information: - - Target Audience: {', '.join(target_audience)} - - Main Points: {', '.join(main_points)} - - Tone and Style: {tone_style} - - Video Length: {video_length} - - Script Structure: {script_structure} - - Specific Instructions: - * Include a strong hook to grab attention at the start. - * Structure the script with clear sections and headings. - * Provide engaging introductions and conclusions for each section. - * Use clear and concise language, avoiding jargon or overly technical terms. - * Tailor the language and tone to the target audience. - * Include relevant examples, anecdotes, and stories to make the video more engaging. - * Add questions to encourage viewer interaction and participation. - * End the script with a strong call to action, encouraging viewers to subscribe, like the video, or visit your website. - - Use Case: {use_case} - - Output Format: - - Please provide the script in a clear and easy-to-read format. - Include clear headings for each section and ensure that all instructions are followed. - """ - - try: - response = generate_text_with_exception_handling(prompt) - return response - except Exception as err: - st.error(f"Exit: Failed to get response from LLM: {err}") - exit(1) diff --git a/lib/ai_writers/youtube_writers/README b/lib/ai_writers/youtube_writers/README new file mode 100644 index 00000000..27708919 --- /dev/null +++ b/lib/ai_writers/youtube_writers/README @@ -0,0 +1,225 @@ +YouTube Description Generator with SEO optimization features. Here's a summary of the improvements I've made: +1. Added SEO Optimization Features +Primary and Secondary Keywords: +Renamed the original keywords field to "Primary Keywords" for clarity +Added a new field for "Secondary Keywords" in the SEO Optimization tab +Updated the prompt generation to include both primary and secondary keywords + +Keyword Density Checker: +Added a new calculate_keyword_density function that: +Counts occurrences of each keyword in the text +Calculates the density as a percentage of total words +Returns a formatted string with density for each keyword +Character Counter and SEO Score: +Added a character counter that displays the total length of the description +Created a comprehensive calculate_seo_score function that evaluates: +Text length (optimal is between 200-5000 characters) +Keyword placement in the first 100 characters +Keyword density (optimal is between 0.5-2.5%) +Presence of call-to-action phrases +Inclusion of hashtags +Presence of links +Returns a percentage score based on these factors + + Improved User Interface +Tabbed Interface: +Organized the interface into three tabs: "Basic Info", "SEO Optimization", and "Advanced Options" +This makes the interface cleaner and more focused +Enhanced Input Fields: +Added more descriptive help text for each field +Improved field organization and grouping +Preview Options: +Added tabs for different views of the generated description: +"Formatted" - Shows the description with proper formatting +"Plain Text" - Shows the raw text for copying +"SEO Analysis" - Shows the SEO metrics and score +Download Option: +Added a download button to save the description as a text file + + Improved Prompt Generation +Dynamic Prompt Building: +Restructured the prompt generation to be more dynamic +Only includes sections that are relevant based on user input +Provides more specific instructions when additional information is available +Template Support: +Added support for different description templates +Includes a custom template option for advanced users +These enhancements make the YouTube Description Generator much more useful for content creators by providing: +Better SEO optimization +More detailed analysis of the generated content +A more organized and user-friendly interface +More customization options +The tool now helps creators not only generate descriptions but also evaluate and optimize them for better performance on YouTube. + +YouTube Title Generator with the following features: +Character Counter: +Tracks the length of each generated title +Indicates if the title is within the optimal length range (50-60 characters) +Provides visual feedback with success/warning messages +Clickbait Detector: +Contains a comprehensive list of clickbait phrases +Calculates a clickbait score based on the presence of these phrases +Provides clear visual feedback about clickbait detection +SEO Score: +Calculates a score out of 10 based on various SEO elements +Considers title length, numbers, question marks, colons, and brackets +Provides visual feedback about the SEO score + +User Interface Improvements: +Displays each title in an expandable section +Shows detailed analysis for each title +Includes a copy button for easy title copying +Provides visual indicators (✅, ⚠️, ❌) for quick assessment + +Script Structure Templates +I've expanded the script structure options from just 3 to 14 different formats: +Problem-Solution: Identifies a problem and presents your solution +Before-After-Bridge: Shows the problem, solution, and transformation +Hook-Problem-Solution-Call to Action: Attention-grabbing format with clear problem, solution, and call to action +Compare and Contrast: Compares different options or approaches +Step-by-Step Tutorial: Detailed instructions broken down into clear steps +Case Study: Examines a specific example or scenario in detail +Interview Format: Structured as an interview with questions and answers +Review Format: Evaluates a product, service, or topic with pros and cons +Vlog Format: Personal, conversational style documenting experiences +Educational Format: Focused on teaching a specific concept or skill +Entertainment Format: Engaging, fun-focused content with humor or excitement +Additional Improvements +Structure Descriptions: Added helpful descriptions for each script structure to help users understand which format best suits their content. +Advanced Options: Added an expandable section with customizable options: +Attention-grabbing hooks +Call-to-action elements +Viewer engagement prompts +Suggested timestamps +Visual cues/transitions with different style options + +Enhanced Script Generation: +Structure-specific instructions for each template +Visual cue instructions for better video production +Improved prompt engineering for more natural, conversational scripts +Better User Experience: +Progress bar during generation +Tabbed preview with formatted and plain text views +Download button for saving scripts +Improved error handling +More Use Cases: Added additional use cases like News Coverage, How-To Guides, Product Demonstrations, Travel Videos, Cooking/Recipe Videos, Gaming Content, and Tech Reviews. +These enhancements make the YouTube Script Generator much more powerful and flexible, allowing content creators to generate scripts tailored to their specific needs and content types. The structure-specific instructions ensure that each script follows best practices for its format, resulting in more professional and engaging content. + +1. Enhanced Engagement Hooks +I've added a variety of engagement hook options that users can select to include in their scripts: +Question Hook: Start with a thought-provoking question +Story Hook: Begin with a brief, relevant story or anecdote +Statistic Hook: Open with an interesting statistic or fact +Controversy Hook: Present a controversial statement to spark interest +Promise Hook: Make a promise about what viewers will learn +Scenario Hook: Describe a relatable scenario +Mystery Hook: Create a sense of mystery or intrigue +Quote Hook: Start with a relevant quote from an expert + + +2. Community Interaction Points +I've added several options for community interaction that can be included in the script: +Comment Prompt: Ask viewers to share experiences in comments +Poll Suggestion: Suggest creating a poll for viewers +Question for Comments: Pose a specific question for comments +Challenge: Challenge viewers to try something and report back +Tag Friends: Encourage tagging friends who might benefit +Share Request: Ask viewers to share the video +Community Post: Mention creating a community post +Live Stream Teaser: Tease an upcoming live stream + +3. Script Export Options +I've implemented a comprehensive export system with multiple format options: +Text (.txt): Simple text format +Markdown (.md): For platforms that support markdown +HTML (.html): Web-friendly format +JSON (.json): Structured data format +Subtitles (SRT): Basic subtitle format for video editing +Additional export features include: +Custom filename option +Copy to clipboard functionality +Formatted and plain text views of the script +Download button with the selected format + +UI Improvements +Added a new "Engagement & Export" tab to organize the new features +Improved script display with tabs for formatted and plain text views +Added a subheader for export options +Included additional export options that can be expanded +These enhancements make the YouTube Script Generator more powerful and user-friendly, providing creators with more tools to engage their audience and export their content in various formats. + +1. YouTube Thumbnail Generator +Added a dedicated tab with a "Coming Soon" notice +Included a comprehensive description of the tool's features: +Thumbnail concept generation based on video content +Color scheme suggestions aligned with brand +Layout recommendations for maximum click-through rate +Best practices for thumbnail design +Text placement suggestions for readability +Added a placeholder image to visually represent the upcoming feature + +2. YouTube Tags Generator + +Created a tab with a "Coming Soon" notice +Provided a detailed description of the tool's capabilities: +Relevant tag generation based on video content +Trending tag suggestions to increase visibility +Tag combination recommendations +Tag research tools for finding popular keywords +Recommendations for tag placement and usage +Added a placeholder image for visual appeal + +3. YouTube End Screen Generator + +Added a tab with a "Coming Soon" notice +Included a description of the tool's features: +End screen template generation based on video type +Strategic CTA placement recommendations +Video playlist promotion suggestions +Best practices for end screen design +Cross-promotion opportunity recommendations +Added a placeholder image to represent the upcoming feature + +4. YouTube Playlist Description Generator + +Created a tab with a "Coming Soon" notice +Provided a description of the tool's capabilities: +Engaging playlist description generation +SEO optimization recommendations for playlists +Playlist organization suggestions +Best practices for playlist metadata +Recommendations for playlist thumbnails and titles +Added a placeholder image for visual appeal + + +5. Additional "More Tools" Tab + +Added an extra tab for future tools +Included a list of potential future features: +YouTube Analytics Insights +Channel Trailer Generator +Video Series Planner +YouTube Shorts Script Generator +Community Post Generator +Added a call for user suggestions for new tools +Included a placeholder image for visual appeal + + +Each tool tab follows a consistent format with: + +A clear title with an emoji for visual identification +A "Coming Soon" notice using Streamlit's info component +A detailed description of the tool's features +A placeholder image to represent the upcoming feature + +This implementation provides users with a clear roadmap of upcoming features while maintaining the existing functionality of the YouTube AI Writer. The "coming soon" state allows you to gauge user interest in these features before fully implementing them. + + + +TBD: +Allow alwrity end users to connect their youtube accounts to fetch their youtube data for analytics and then generate YT related content based on their data and needs: + +1). https://developers.google.com/youtube/reporting/v1/code_samples/python +2). https://github.com/youtube/api-samples/blob/master/python/yt_analytics_report.py +3). https://developers.google.com/youtube/reporting/guides/authorization/server-side-web-apps#python + diff --git a/lib/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md b/lib/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md new file mode 100644 index 00000000..9e97aac5 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/README_Thumbnail_Generator.md @@ -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.* \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/modules/README_endScreen.md b/lib/ai_writers/youtube_writers/modules/README_endScreen.md new file mode 100644 index 00000000..81f15c33 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/README_endScreen.md @@ -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? diff --git a/lib/ai_writers/youtube_writers/modules/__init__.py b/lib/ai_writers/youtube_writers/modules/__init__.py new file mode 100644 index 00000000..e2009af3 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/__init__.py @@ -0,0 +1,5 @@ +""" +YouTube AI Writer Modules + +This package contains modular components for the YouTube AI Writer functionality. +""" \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/modules/description_generator.py b/lib/ai_writers/youtube_writers/modules/description_generator.py new file mode 100644 index 00000000..66661ae1 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/description_generator.py @@ -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 \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/modules/end_screen_generator.py b/lib/ai_writers/youtube_writers/modules/end_screen_generator.py new file mode 100644 index 00000000..861821e8 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/end_screen_generator.py @@ -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.") \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/modules/script_generator.py b/lib/ai_writers/youtube_writers/modules/script_generator.py new file mode 100644 index 00000000..d6769b91 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/script_generator.py @@ -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"
{script}
" + 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.") \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/modules/thumbnail_generator.py b/lib/ai_writers/youtube_writers/modules/thumbnail_generator.py new file mode 100644 index 00000000..44465681 --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/thumbnail_generator.py @@ -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.") \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/modules/title_generator.py b/lib/ai_writers/youtube_writers/modules/title_generator.py new file mode 100644 index 00000000..7f56f18d --- /dev/null +++ b/lib/ai_writers/youtube_writers/modules/title_generator.py @@ -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(""" +
+

Generated YouTube Titles

+

Click on a title to see detailed analysis and copy options

+
+ """, 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""" +
+

{title}

+
+ """, 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"

{analysis['seo_score']}/10

", 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""" + + """, + 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(""" +
+

Generated YouTube Titles

+

Click on a title to see detailed analysis and copy options

+
+ """, 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""" +
+

{title}

+
+ """, 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"

{analysis['seo_score']}/10

", 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""" + + """, + unsafe_allow_html=True + ) + st.success(f"✅ Title {i} copied to clipboard!") \ No newline at end of file diff --git a/lib/ai_writers/youtube_writers/youtube_ai_writer.py b/lib/ai_writers/youtube_writers/youtube_ai_writer.py new file mode 100644 index 00000000..73cc48c5 --- /dev/null +++ b/lib/ai_writers/youtube_writers/youtube_ai_writer.py @@ -0,0 +1,242 @@ +""" +YouTube AI Writer + +This module provides a comprehensive suite of tools for generating YouTube content. +""" + +import streamlit as st +import importlib +import sys +import os +from pathlib import Path +from .modules.title_generator import write_yt_title +from .modules.description_generator import write_yt_description +from .modules.script_generator import write_yt_script +from .modules.thumbnail_generator import write_yt_thumbnail +from .modules.end_screen_generator import write_yt_end_screen + + +def youtube_main_menu(): + """Main function for the YouTube AI Writer.""" + + # Initialize session state for selected tool if it doesn't exist + if "selected_tool" not in st.session_state: + st.session_state.selected_tool = None + + # Define the YouTube tools with their details + youtube_tools = [ + # Content Creation Tools + { + "name": "YT Title Generator", + "icon": "📝", + "description": "Create engaging YouTube video titles that drive clicks and views.", + "color": "#FF0000", # YouTube red + "category": "Content Creation", + "function": write_yt_title, + "status": "active" + }, + { + "name": "YT Description Generator", + "icon": "📄", + "description": "Generate SEO-optimized descriptions for your YouTube videos.", + "color": "#FF0000", # YouTube red + "category": "Content Creation", + "function": write_yt_description, + "status": "active" + }, + { + "name": "YT Script Generator", + "icon": "🎬", + "description": "Create professional YouTube scripts with optimized structures for engagement.", + "color": "#FF0000", # YouTube red + "category": "Content Creation", + "function": write_yt_script, + "status": "active" + }, + + # Optimization Tools + { + "name": "Thumbnail Generator", + "icon": "🎨", + "description": "Create engaging thumbnail ideas and descriptions with color scheme suggestions based on your brand.", + "color": "#FF0000", # YouTube red + "category": "Optimization", + "function": write_yt_thumbnail, + "status": "active" + }, + { + "name": "Tags Generator", + "icon": "🏷️", + "description": "Generate optimized tags for your videos with trending tag suggestions to improve discoverability.", + "color": "#CC0000", # Darker red for coming soon + "category": "Optimization", + "function": None, + "status": "coming_soon" + }, + + # Engagement Tools (Coming Soon) + { + "name": "End Screen Generator", + "icon": "🎬", + "description": "Create effective end screen content and CTAs with template suggestions based on video type.", + "color": "#FF0000", # YouTube red + "category": "Engagement", + "function": write_yt_end_screen, + "status": "active" + }, + { + "name": "Playlist Description Generator", + "icon": "📚", + "description": "Generate SEO-optimized descriptions for your playlists with organization suggestions.", + "color": "#CC0000", # Darker red for coming soon + "category": "Engagement", + "function": None, + "status": "coming_soon" + }, + + # Future Tools (Coming Soon) + { + "name": "Analytics Insights", + "icon": "📊", + "description": "Get AI-powered insights and recommendations based on your channel analytics.", + "color": "#990000", # Even darker red for future + "category": "Future Tools", + "function": None, + "status": "future" + }, + { + "name": "Channel Trailer Generator", + "icon": "🎥", + "description": "Create compelling channel trailers that convert visitors into subscribers.", + "color": "#990000", # Even darker red for future + "category": "Future Tools", + "function": None, + "status": "future" + }, + { + "name": "Video Series Planner", + "icon": "📅", + "description": "Plan and organize your video series with content calendars and topic ideas.", + "color": "#990000", # Even darker red for future + "category": "Future Tools", + "function": None, + "status": "future" + }, + { + "name": "Shorts Script Generator", + "icon": "📱", + "description": "Create engaging scripts optimized for YouTube Shorts format.", + "color": "#990000", # Even darker red for future + "category": "Future Tools", + "function": None, + "status": "future" + }, + { + "name": "Community Post Generator", + "icon": "💬", + "description": "Generate engaging community posts to keep your audience active between videos.", + "color": "#990000", # Even darker red for future + "category": "Future Tools", + "function": None, + "status": "future" + } + ] + + # Create a container for the dashboard + dashboard_container = st.container() + + # Create a container for the tool input section + tool_container = st.container() + + # If a tool is selected, show its input section + if st.session_state.selected_tool is not None: + with tool_container: + # Display the selected tool's input section + st.markdown("---") + st.markdown(f"# {st.session_state.selected_tool['icon']} {st.session_state.selected_tool['name']}") + + # Add a back button + if st.button("← Back to Dashboard", key="back_to_dashboard"): + # Clear the selected tool from session state + st.session_state.selected_tool = None + st.rerun() + + # Call the function for the selected tool + if st.session_state.selected_tool["function"]: + # Directly call the function instead of using it as a reference + st.session_state.selected_tool["function"]() + else: + # Display coming soon or future tool information + st.info(f"**{st.session_state.selected_tool['status'].replace('_', ' ').title()}!**") + st.write(st.session_state.selected_tool["description"]) + st.image(f"https://via.placeholder.com/600x300?text={st.session_state.selected_tool['name']}+Coming+Soon", use_column_width=True) + else: + with dashboard_container: + # Display the dashboard + # Header + st.markdown(""" +
+

🎥 YouTube AI Writer

+

Generate professional YouTube content with AI-powered tools

+
+ """, unsafe_allow_html=True) + + # Introduction + st.markdown(""" + ## Welcome to the YouTube AI Writer Suite + + This dashboard provides access to a variety of tools for creating and optimizing YouTube content. + Select a tool below to get started with generating professional content for your channel. + + ### How to Use This Dashboard + + 1. Browse the available tools below + 2. Click on a tool card to access its specific functionality + 3. Fill in the required information + 4. Generate high-quality content for your YouTube channel + """) + + # Group tools by category + categories = {} + for tool in youtube_tools: + category = tool["category"] + if category not in categories: + categories[category] = [] + categories[category].append(tool) + + # Display tools by category + for category, tools in categories.items(): + st.markdown(f"## {category}") + + # Create a 3-column layout for the tool cards + cols = st.columns(3) + + # Display the tool cards + for i, tool in enumerate(tools): + # Determine which column to use + col = cols[i % 3] + + with col: + # Create a card for each tool + status_badge = "" + if tool["status"] == "coming_soon": + status_badge = "Coming Soon" + elif tool["status"] == "future": + status_badge = "Future" + + st.markdown(f""" +
+

{tool["icon"]} {tool["name"]} {status_badge}

+

{tool["description"]}

+
+ """, unsafe_allow_html=True) + + # Add a button to access the tool + if st.button(f"Use {tool['name']}", key=f"btn_{tool['name']}"): + # Store the selected tool in session state + st.session_state.selected_tool = tool + st.rerun() + + +if __name__ == "__main__": + youtube_ai_writer() diff --git a/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py b/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py new file mode 100644 index 00000000..bfb6d852 --- /dev/null +++ b/lib/gpt_providers/text_to_image_generation/gen_gemini_images.py @@ -0,0 +1,377 @@ +import os +from PIL import Image +from io import BytesIO +import PIL +import streamlit as st +from google import genai +from google.genai import types +import logging +import datetime +import base64 +import random +import time + + +from .save_image import save_generated_image + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger('gemini_image_generator') + +# With image generation in Gemini, your imagination is the limit. +# If what you see doesn't quite match what you had in mind, try adding more details to the prompt. +# The more specific you are, the better Gemini can create images that reflect your vision. + +# Generate images using Gemini +# Gemini 2.0 Flash Experimental supports the ability to output text and inline images. +# This lets you use Gemini to conversationally edit images or generate outputs with interwoven text (for example, generating a blog post with text and images in a single turn). +# Note: Make sure to include responseModalities: ["Text", "Image"] in your generation configuration for text and image output with gemini-2.0-flash-exp-image-generation. Image only is not allowed. + + +class AIPromptGenerator: + """ + Generates enhanced AI image prompts based on user keywords, + following the guidelines of the Imagen documentation. + """ + + def __init__(self): + self.photography_styles = ["photo", "photograph"] + self.art_styles = ["painting", "sketch", "drawing", "illustration", "digital art", "render"] + self.art_techniques = ["technical pencil drawing", "charcoal drawing", "color pencil drawing", "pastel painting", "digital art", "art deco (poster)", "impressionist painting", "renaissance painting", "pop art"] + self.camera_proximity = ["close-up", "zoomed out", "taken from far away"] + self.camera_position = ["aerial", "from below"] + self.lighting = ["natural lighting", "dramatic lighting", "warm lighting", "cold lighting", "studio lighting", "golden hour lighting"] + self.camera_settings = ["motion blur", "soft focus", "bokeh", "portrait"] + self.lens_types = ["35mm lens", "50mm lens", "fisheye lens", "wide angle lens", "macro lens", "telephoto lens"] + self.film_types = ["black and white film", "polaroid"] + self.materials = ["made of cheese", "made of paper", "made of neon tubes", "metallic", "glass", "wooden", "stone"] + self.shapes = ["in the shape of a bird", "angular", "curved", "geometric"] + self.quality_modifiers_general = ["high-quality", "beautiful", "stylized", "detailed", "epic", "grand"] + self.quality_modifiers_photo = ["4K", "HDR", "studio photo", "professional photo", "photorealistic"] + self.quality_modifiers_art = ["by a professional artist", "intricate details", "masterpiece"] + self.aspect_ratios = ["1:1 aspect ratio", "4:3 aspect ratio", "3:4 aspect ratio", "16:9 aspect ratio", "9:16 aspect ratio"] + self.photorealistic_modifiers = { + "portraits": ["prime lens", "zoom lens", "24-35mm", "black and white film", "film noir", "shallow depth of field", "duotone (mention two colors)"], + "objects": ["macro lens", "60-105mm", "high detail", "precise focusing", "controlled lighting"], + "motion": ["telephoto zoom lens", "100-400mm", "fast shutter speed", "action shot", "movement tracking"], + "wide-angle": ["wide-angle lens", "10-24mm", "long exposure", "sharp focus", "smooth water or clouds", "astro photography"] + } + + def generate_prompt(self, keywords): + """ + Generates an enhanced AI image prompt based on user-provided keywords. + + Args: + keywords (list): A list of keywords describing the desired image. + + Returns: + str: An enhanced AI image prompt. + """ + if not keywords: + return "A beautiful image." + + prompt_parts = [] + subject = " ".join(keywords) + prompt_parts.append(subject) + + # Add context and background (optional) + context_options = ["in a detailed background", "outdoors", "indoors", "in a studio", "with a blurred background"] + if random.random() < 0.6: # Add context with a probability + prompt_parts.append(random.choice(context_options)) + + # Add style (optional) + style_options = self.photography_styles + [f"{art} of" for art in self.art_styles] + if random.random() < 0.7: + prompt_parts.insert(0, random.choice(style_options)) + if prompt_parts[0].startswith("painting of") or prompt_parts[0].startswith("sketch of") or prompt_parts[0].startswith("drawing of"): + if random.random() < 0.5: + prompt_parts.append(f"in the style of {random.choice(self.art_techniques)}") + + # Add photography modifiers (if photography style is chosen) + if any(style in prompt_parts[0] for style in self.photography_styles): + if random.random() < 0.4: + prompt_parts.append(random.choice(self.camera_proximity)) + if random.random() < 0.3: + prompt_parts.append(random.choice(self.camera_position)) + if random.random() < 0.5: + prompt_parts.append(random.choice(self.lighting)) + if random.random() < 0.3: + prompt_parts.append(random.choice(self.camera_settings)) + if random.random() < 0.2: + prompt_parts.append(random.choice(self.lens_types)) + if random.random() < 0.1: + prompt_parts.append(random.choice(self.film_types)) + + # Add shapes and materials (optional) + if random.random() < 0.3: + prompt_parts.append(random.choice(self.materials)) + if random.random() < 0.2: + prompt_parts.append(random.choice(self.shapes)) + + # Add quality modifiers (optional) + if random.random() < 0.6: + quality_options = self.quality_modifiers_general + if any(style in prompt_parts[0] for style in self.photography_styles): + quality_options += self.quality_modifiers_photo + else: + quality_options += self.quality_modifiers_art + prompt_parts.append(random.choice(list(set(quality_options)))) # Avoid duplicates + + # Add aspect ratio (optional) + if random.random() < 0.2: + prompt_parts.append(random.choice(self.aspect_ratios)) + + return ", ".join(prompt_parts) + + def generate_photorealistic_prompt(self, keywords, focus=""): + """ + Generates an enhanced AI image prompt specifically for photorealistic images. + + Args: + keywords (list): A list of keywords describing the desired image. + focus (str, optional): The focus of the photorealistic image (e.g., "portraits", "objects", "motion", "wide-angle"). Defaults to "". + + Returns: + str: An enhanced photorealistic AI image prompt. + """ + if not keywords: + return "A photorealistic image." + + prompt_parts = ["A photo of", "photorealistic"] + prompt_parts.append(" ".join(keywords)) + + if focus and focus in self.photorealistic_modifiers: + modifiers = self.photorealistic_modifiers[focus] + if modifiers: + num_modifiers = random.randint(1, min(3, len(modifiers))) + selected_modifiers = random.sample(modifiers, num_modifiers) + prompt_parts.extend(selected_modifiers) + + # Add general quality modifiers + if random.random() < 0.5: + prompt_parts.append(random.choice(self.quality_modifiers_photo)) + + # Add lighting + if random.random() < 0.4: + prompt_parts.append(random.choice(self.lighting)) + + return ", ".join(prompt_parts) + + +def generate_gemini_image(prompt, keywords=None, style=None, focus=None, enhance_prompt=True, max_retries=3, initial_retry_delay=2): + """ + Generate images using Gemini + Depending on the prompt and context, Gemini will generate content in different modes (text to image, text to image and text, etc.). + Here are some examples: + + 1). Text to image + Example prompt: "Generate an image of the Eiffel tower with fireworks in the background." + 2). Text to image(s) and text (interleaved) + Example prompt: "Generate an illustrated recipe for a paella." + + Image generation may not always trigger: + - The model may output text only. Try asking for image outputs explicitly (e.g. "generate an image", "provide images as you go along", "update the image"). + - The model may stop generating partway through. Try again or try a different prompt. + + Args: + prompt (str): The prompt to generate the image from. + keywords (list, optional): Keywords to enhance the prompt. Defaults to None. + style (str, optional): The style of the image. Defaults to None. + focus (str, optional): The focus of the image (e.g., "portraits", "objects", "motion", "wide-angle"). Defaults to None. + enhance_prompt (bool, optional): Whether to enhance the prompt using AIPromptGenerator. Defaults to True. + max_retries (int, optional): Maximum number of retry attempts for handling 503 errors. Defaults to 3. + initial_retry_delay (int, optional): Initial delay in seconds before retrying. Defaults to 2. + + Returns: + str: The path to the generated image. + """ + logger.info(f"Generating image with prompt: '{prompt[:100]}...'") + + # Enhance the prompt if requested + if enhance_prompt and keywords: + prompt_generator = AIPromptGenerator() + if style == "photorealistic" and focus: + logger.info(f"Generating photorealistic prompt with focus: {focus}") + enhanced_prompt = prompt_generator.generate_photorealistic_prompt(keywords, focus) + else: + logger.info("Generating enhanced prompt") + enhanced_prompt = prompt_generator.generate_prompt(keywords) + + # Combine the enhanced prompt with the original prompt + prompt = f"{prompt}\n\nEnhanced prompt: {enhanced_prompt}" + logger.info(f"Final prompt: '{prompt[:100]}...'") + + retry_count = 0 + retry_delay = initial_retry_delay + + while retry_count <= max_retries: + try: + client = genai.Client(api_key=os.getenv("GEMINI_API_KEY")) + contents = (prompt) + + logger.info("Sending request to Gemini API") + response = client.models.generate_content( + model="gemini-2.0-flash-exp-image-generation", + contents=contents, + config=types.GenerateContentConfig( + response_modalities=['Text', 'Image'] + ) + ) + logger.info("Received response from Gemini API") + + img_name = None + for part in response.candidates[0].content.parts: + if part.text is not None: + logger.info(f"Received text response: '{part.text[:100]}...'") + print(part.text) + elif part.inline_data is not None: + logger.info("Received image data from Gemini") + image = Image.open(BytesIO((part.inline_data.data))) + image.show() + if part.text is not None: + img_name = f'{part.text}-gemini-native-image.png' + else: + img_name = f'gemini-native-image-{datetime.datetime.now().strftime("%Y%m%d-%H%M%S")}.png' + try: + logger.info(f"Saving image to: {img_name}") + image.save(img_name) + + # Create a dictionary with the expected format for save_generated_image + img_response = { + "artifacts": [ + { + "base64": base64.b64encode(open(img_name, "rb").read()).decode('utf-8') + } + ] + } + + # Call save_generated_image with the correct format + save_generated_image(img_response) + except Exception as err: + logger.error(f"Failed to save image: {err}") + st.error(f"Failed to save image: {err}") + + logger.info(f"Image generation completed. Image name: {img_name}") + return img_name + except Exception as err: + error_message = str(err) + logger.error(f"Error in generate_gemini_image: {err}") + + # Check if this is a 503 UNAVAILABLE error + if "503 UNAVAILABLE" in error_message and retry_count < max_retries: + retry_count += 1 + logger.info(f"Model is overloaded. Retrying in {retry_delay} seconds (attempt {retry_count}/{max_retries})") + st.warning(f"The image generation service is currently busy. Retrying in {retry_delay} seconds...") + time.sleep(retry_delay) + # Exponential backoff + retry_delay *= 2 + else: + st.error(f"Error generating image: {err}") + return None + + # If we've exhausted all retries + st.error("The image generation service is currently unavailable. Please try again later.") + return None + + +def edit_image(image_path, prompt, max_retries=3, initial_retry_delay=2): + """ + - Image editing (text and image to image) + Example prompt: "Edit this image to make it look like a cartoon" + Example prompt: [image of a cat] + [image of a pillow] + "Create a cross stitch of my cat on this pillow." + + - Multi-turn image editing (chat) + Example prompts: [upload an image of a blue car.] "Turn this car into a convertible." "Now change the color to yellow." + + Image editing with Gemini + To perform image editing, add an image as input. + The following example demonstrats uploading base64 encoded images. + For multiple images and larger payloads, check the image input section. + + Args: + image_path (str): The path to the image to edit. + prompt (str): The prompt to edit the image with. + max_retries (int, optional): Maximum number of retry attempts for handling 503 errors. Defaults to 3. + initial_retry_delay (int, optional): Initial delay in seconds before retrying. Defaults to 2. + + Returns: + str: The path to the edited image. + """ + import PIL.Image + image = PIL.Image.open(image_path) + + retry_count = 0 + retry_delay = initial_retry_delay + + while retry_count <= max_retries: + try: + client = genai.Client() + text_input = (prompt) + + logger.info("Sending request to Gemini API for image editing") + response = client.models.generate_content( + model="gemini-2.0-flash-exp-image-generation", + contents=[text_input, image], + config=types.GenerateContentConfig( + response_modalities=['Text', 'Image'] + ) + ) + logger.info("Received response from Gemini API for image editing") + + edited_img_name = None + for part in response.candidates[0].content.parts: + if part.text is not None: + logger.info(f"Received text response: '{part.text[:100]}...'") + st.write(part.text) + elif part.inline_data is not None: + logger.info("Received edited image data from Gemini") + edited_image = Image.open(BytesIO(part.inline_data.data)) + edited_image.show() + + # Save the edited image + edited_img_name = f'edited-{os.path.basename(image_path)}' + try: + logger.info(f"Saving edited image to: {edited_img_name}") + edited_image.save(edited_img_name) + + # Create a dictionary with the expected format for save_generated_image + img_response = { + "artifacts": [ + { + "base64": base64.b64encode(open(edited_img_name, "rb").read()).decode('utf-8') + } + ] + } + + # Call save_generated_image with the correct format + save_generated_image(img_response) + except Exception as err: + logger.error(f"Failed to save edited image: {err}") + st.error(f"Failed to save edited image: {err}") + + logger.info(f"Image editing completed. Edited image name: {edited_img_name}") + return edited_img_name + except Exception as err: + error_message = str(err) + logger.error(f"Error in edit_image: {err}") + + # Check if this is a 503 UNAVAILABLE error + if "503 UNAVAILABLE" in error_message and retry_count < max_retries: + retry_count += 1 + logger.info(f"Model is overloaded. Retrying in {retry_delay} seconds (attempt {retry_count}/{max_retries})") + st.warning(f"The image editing service is currently busy. Retrying in {retry_delay} seconds...") + time.sleep(retry_delay) + # Exponential backoff + retry_delay *= 2 + else: + st.error(f"Error editing image: {err}") + return None + + # If we've exhausted all retries + st.error("The image editing service is currently unavailable. Please try again later.") + return None + + diff --git a/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py b/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py index 227b2381..92c1eb50 100644 --- a/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py +++ b/lib/gpt_providers/text_to_image_generation/main_generate_image_from_prompt.py @@ -25,7 +25,7 @@ logger.add(sys.stdout, from .gen_dali3_images import generate_dalle3_images from .gen_stabl_diff_img import generate_stable_diffusion_image from ..text_generation.main_text_generation import llm_text_gen - +from .gen_gemini_images import generate_gemini_image def generate_image(user_prompt): """ @@ -44,7 +44,7 @@ def generate_image(user_prompt): --> user (str): A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. """ # FIXME: Need to remove default value to match sidebar input. - image_engine = 'Stability-AI' + image_engine = 'Gemini-AI' image_stored_at = None if user_prompt: @@ -57,6 +57,9 @@ def generate_image(user_prompt): logger.info(f"Calling Stable diffusion text-to-image with prompt: \n{img_prompt}") print("\n\n") image_stored_at = generate_stable_diffusion_image(img_prompt) + elif 'Gemini-AI' in image_engine: + logger.info(f"Calling Gemini text-to-image with prompt: \n{img_prompt}") + image_stored_at = generate_gemini_image(img_prompt) return image_stored_at except Exception as err: logger.error(f"Failed to generate Image: {err}") diff --git a/lib/utils/alwrity_utils.py b/lib/utils/alwrity_utils.py index 64a3d446..c3009a57 100644 --- a/lib/utils/alwrity_utils.py +++ b/lib/utils/alwrity_utils.py @@ -14,7 +14,7 @@ from lib.ai_writers.facebook_ai_writer import facebook_post_writer from lib.ai_writers.linkedin_ai_writer import linked_post_writer from lib.ai_writers.twitter_ai_writer import tweet_writer from lib.ai_writers.insta_ai_writer import insta_writer -from lib.ai_writers.youtube_ai_writer import write_yt_title, write_yt_description, write_yt_script +from lib.ai_writers.youtube_writers.youtube_ai_writer import youtube_main_menu from lib.ai_writers.web_url_ai_writer import blog_from_url from lib.ai_writers.image_ai_writer import blog_from_image from lib.ai_writers.ai_essay_writer import ai_essay_generator @@ -485,7 +485,7 @@ def ai_social_writer(): ("linkedin", "LinkedIn"), ("twitter", "Twitter"), ("instagram", "Instagram"), - ("youtube", "YouTube") # Add YouTube + ("youtube", "YouTube") ] # Selectbox for choosing a platform @@ -498,13 +498,5 @@ def ai_social_writer(): tweet_writer() elif "instagram" in selected_platform: insta_writer() -# elif "youtube" in selected_platform: -# options = ["Write YT Description", "Write YT Title", "Write YT Script"] -# selected_option = st.radio("", options) -# -# if selected_option == "Write YT Description": -# write_yt_description() -# elif selected_option == "Write YT Title": -# write_yt_title() -# elif selected_option == "Write YT Script": -# write_yt_script() + elif "youtube" in selected_platform: + youtube_main_menu()