Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from dotenv import load_dotenv | |
| import google.generativeai as genai | |
| import os | |
| from youtube_transcript_api import YouTubeTranscriptApi | |
| import time | |
| import re | |
| import tiktoken | |
| import time | |
| from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type | |
| from google.api_core.exceptions import ResourceExhausted | |
| # Load environment variables from a .env file | |
| load_dotenv() | |
| genai.configure(api_key='AIzaSyBMb20Nck_BV_4297NnxlEju73UL5vsvYY') | |
| def generate_content_with_retry(model, prompt): | |
| time.sleep(2) # Increased delay between API calls | |
| try: | |
| return model.generate_content(prompt) | |
| except ResourceExhausted as e: | |
| st.warning(f"API quota exceeded. Retrying in a moment... ({e})") | |
| raise e | |
| # Define the base prompt template | |
| base_prompt_template = ''' | |
| You are an expert content writer and storyteller with years of experience in creating viral blog posts. Your task is to transform the input content into a captivating, human-like blog post that feels personally written and connects deeply with readers. | |
| Guidelines for Creating an Authentic, Engaging Blog Post: | |
| 1. Voice & Personality: | |
| - Write as if you're having a coffee chat with the reader | |
| - Include personal observations and insights | |
| - Use "I", "we", and "you" to create connection | |
| - Add thoughtful rhetorical questions to engage readers | |
| - Include relevant personal anecdotes or examples | |
| 2. Enhanced Structure: | |
| - Hook: Start with a powerful personal story or provocative question | |
| - Introduction: Create an emotional connection with the reader's pain points | |
| - Story Arc: Maintain narrative tension throughout | |
| - Strategic Cliffhangers: Keep readers engaged between sections | |
| - Memorable Conclusion: End with inspiration or call-to-action | |
| 3. Human Touch Elements: | |
| - Add occasional conversational asides (e.g., "Now, here's the interesting part...") | |
| - Include relatable real-world examples | |
| - Share practical tips from personal experience | |
| - Address potential reader objections naturally | |
| - Use humor and wit where appropriate | |
| 4. Engagement Boosters: | |
| - Create "Aha!" moments | |
| - Include surprising statistics or counterintuitive insights | |
| - Add social proof through expert quotes or case studies | |
| - Use power words and emotional triggers | |
| - Create shareable, quotable moments | |
| 5. Modern Content Optimization: | |
| - Write scannable content with varied paragraph lengths | |
| - Use bucket brigades to maintain flow | |
| - Include tweet-worthy quotes | |
| - Add content upgrades or bonus tips | |
| - Suggest related resources | |
| 6. Visual Flow: | |
| - Use descriptive scene-setting | |
| - Include sensory details | |
| - Suggest relevant image placements | |
| - Break up text with varied formatting | |
| - Create visual hierarchy with subheadings | |
| 7. Viral Elements: | |
| - Include controversial or debate-worthy points | |
| - Add "share-worthy" statistics or facts | |
| - Create memorable metaphors | |
| - Include practical takeaways | |
| - End with discussion-provoking questions | |
| 8. Reader Experience: | |
| - Address common objections preemptively | |
| - Include FAQs in conversational style | |
| - Add expert tips and insider secrets | |
| - Provide actionable next steps | |
| - Create FOMO (Fear of Missing Out) elements | |
| 9. Content Enhancement: | |
| - Add relevant industry trends | |
| - Include success stories or case studies | |
| - Provide practical implementation steps | |
| - Share common mistakes to avoid | |
| - Offer exclusive insights | |
| 10. SEO & Readability: | |
| - Natural keyword integration: {keywords} | |
| - Use power words and emotional triggers | |
| - Create skimmable sections | |
| - Include meta description and title suggestions | |
| - Optimize for featured snippets | |
| Tone Guidelines: | |
| - Maintain a {tone} voice throughout | |
| - Balance expertise with accessibility | |
| - Use conversational language | |
| - Show personality and authenticity | |
| - Be empathetic and understanding | |
| Length: Aim for {word_count} words while maintaining quality and engagement | |
| Please transform the following input into a captivating, human-like blog post that readers won't be able to resist sharing: | |
| {input_text} | |
| Remember: Write as if you're the world's most engaging storyteller sharing invaluable insights with a friend. Make every word count and every paragraph impossible to skip. | |
| ''' | |
| # Expanded tone options | |
| TONE_OPTIONS = [ | |
| "Professional", "Casual", "Humorous", "Inspirational", "Educational", | |
| "Conversational", "Formal", "Enthusiastic", "Empathetic", "Authoritative" | |
| ] | |
| # Article length options | |
| LENGTH_OPTIONS = { | |
| "Medium (1000-1500 words)": 1250, | |
| "Long (1500-2500 words)": 2000, | |
| "Extra Long (2500-3500 words)": 3000, | |
| "Comprehensive (3500-5000 words)": 4250 | |
| } | |
| # Function to extract video ID from various YouTube URL formats | |
| def extract_video_id(url): | |
| patterns = [ | |
| r'(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?(?:embed\/)?(?:v\/)?(?:shorts\/)?(?:live\/)?(?:feature=player_embedded&v=)?([^?&"\'>]+)', | |
| ] | |
| for pattern in patterns: | |
| match = re.search(pattern, url) | |
| if match: | |
| return match.group(1) | |
| return None | |
| import threading | |
| # Cache for storing processed data | |
| cache = {} | |
| cache_lock = threading.Lock() | |
| # Optimized transcript fetching with caching | |
| def get_transcript(youtube_video_url): | |
| """Get transcript from YouTube video with verification dropdown""" | |
| try: | |
| video_id = youtube_video_url.split("=")[1] | |
| transcript_text = YouTubeTranscriptApi.get_transcript(video_id, languages=['en-IN', 'en', 'hi']) | |
| transcript = " ".join([entry["text"] for entry in transcript_text]) | |
| # Store transcript in session state for verification | |
| st.session_state.current_transcript = transcript | |
| # Add expandable section to verify transcript | |
| with st.expander("π View Raw Transcript", expanded=False): | |
| st.markdown("### Raw Transcript") | |
| st.markdown("*Verify the transcript before generating the blog post:*") | |
| # Display transcript with scroll | |
| st.markdown( | |
| f""" | |
| <div style="max-height: 300px; overflow-y: scroll; padding: 10px; | |
| border: 1px solid #ccc; border-radius: 5px; background-color: #f5f5f5;"> | |
| {transcript} | |
| </div> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| # Add word count info | |
| word_count = len(transcript.split()) | |
| st.info(f"π Word Count: {word_count} words") | |
| # Add transcript quality warning if needed | |
| if word_count < 100: | |
| st.warning("β οΈ The transcript seems quite short. This might affect the quality of the generated blog post.") | |
| return transcript | |
| except Exception as e: | |
| st.error(f"Error retrieving transcript: {e}") | |
| return None | |
| class ResponseManager: | |
| def __init__(self, model): | |
| self.model = model | |
| self.MAX_TOKENS = 8192 # Adjust based on the model's limit | |
| self.BATCH_SIZE = 1000 # Adjust as needed | |
| def count_tokens(self, text: str) -> int: | |
| try: | |
| encoding = tiktoken.encoding_for_model("gemini-1.5-flash") # Use the correct model name | |
| return len(encoding.encode(text)) | |
| except Exception: | |
| return len(text.split()) * 1.3 # Fallback approximation | |
| def generate_response(self, prompt, temperature, placeholder): | |
| full_response = "" | |
| continuation_prompt = "\nPlease continue from where you left off..." | |
| current_prompt = prompt | |
| try: | |
| while True: | |
| remaining_tokens = self.MAX_TOKENS - self.count_tokens(full_response) | |
| tokens_to_generate = min(self.BATCH_SIZE, remaining_tokens) | |
| response = self.model.generate_content( | |
| current_prompt, | |
| generation_config=genai.types.GenerationConfig( | |
| temperature=temperature, | |
| max_output_tokens=tokens_to_generate, | |
| ), | |
| stream=True | |
| ) | |
| batch_response = "" | |
| for chunk in response: | |
| if chunk.text: | |
| batch_response += chunk.text | |
| full_response += chunk.text | |
| placeholder.markdown(full_response + "β") | |
| time.sleep(0.01) | |
| if batch_response.strip().endswith((".", "!", "?", "\n")) or \ | |
| len(batch_response.strip()) < tokens_to_generate * 0.9: | |
| break | |
| current_prompt = full_response + continuation_prompt | |
| return full_response | |
| except Exception as e: | |
| st.error(f"An error occurred: {str(e)}") | |
| return f"Error generating response: {str(e)}" | |
| def generate_blog_post(input_text, tone, keywords, length): | |
| """Generate a complete blog post using ResponseManager""" | |
| word_count = LENGTH_OPTIONS[length] | |
| model = genai.GenerativeModel("gemini-1.5-flash") | |
| response_manager = ResponseManager(model) | |
| try: | |
| # Show processing status | |
| status_container = st.empty() | |
| status_container.info("π Starting blog post generation...") | |
| # Prepare the prompt | |
| prompt = f""" | |
| {base_prompt_template} | |
| Use the following input to create a single, cohesive blog post: | |
| {input_text} | |
| Ensure the blog post: | |
| - Has a consistent {tone} tone | |
| - Incorporates these keywords: {', '.join(keywords)} | |
| - Is approximately {word_count} words long | |
| - Flows smoothly and reads as a single, coherent piece | |
| """ | |
| # Generate content with ResponseManager | |
| status_container.info("π― Generating blog post...") | |
| blog_post = response_manager.generate_response(prompt, temperature=0.7, placeholder=status_container) | |
| status_container.success("β¨ Blog post generated successfully!") | |
| return blog_post | |
| except Exception as e: | |
| st.error(f"β Error generating blog post: {str(e)}") | |
| return None | |
| # Streamlit UI with progress tracking | |
| def main(): | |
| st.set_page_config(page_title="BlogBrain Genius AI", layout="wide") | |
| # Initialize session state | |
| if 'blog_post' not in st.session_state: | |
| st.session_state.blog_post = None | |
| if 'processing' not in st.session_state: | |
| st.session_state.processing = False | |
| if 'cancel_generation' not in st.session_state: | |
| st.session_state.cancel_generation = False | |
| st.title("βοΈ BlogBrain Genius AI: Video to Blog Alchemist") | |
| # Input method selection | |
| input_method = st.radio("Choose input method:", ("YouTube Video", "Custom Text")) | |
| input_text = "" | |
| if input_method == "YouTube Video": | |
| youtube_url = st.text_input("Enter YouTube URL:") | |
| if youtube_url and not st.session_state.processing: | |
| try: | |
| with st.spinner("Fetching transcript..."): | |
| input_text = get_transcript(youtube_url) | |
| except Exception as e: | |
| st.error(f"Error: {str(e)}") | |
| else: | |
| input_text = st.text_area("Enter your content:", height=200) | |
| # Sidebar options | |
| with st.sidebar: | |
| st.markdown("<h1 style='text-align: center; color: #4A90E2;'>π§ BlogBrain Genius AI</h1>", unsafe_allow_html=True) | |
| st.markdown("<p style='text-align: center;'>Transform Content into Engaging Blog Posts</p>", unsafe_allow_html=True) | |
| st.markdown("---") | |
| tone = st.selectbox("Select tone:", TONE_OPTIONS) | |
| keywords = st.text_input("Enter keywords (comma-separated):") | |
| length = st.selectbox("Select length:", list(LENGTH_OPTIONS.keys())) | |
| # Generate button | |
| if st.button("Generate Blog Post") and input_text: | |
| st.session_state.processing = True | |
| st.session_state.cancel_generation = False | |
| try: | |
| with st.spinner("Generating a single, comprehensive blog post..."): | |
| # Add a cancel button | |
| if st.button("Cancel Generation"): | |
| st.session_state.cancel_generation = True | |
| blog_post = generate_blog_post( | |
| input_text, | |
| tone, | |
| keywords.split(",") if keywords else [], | |
| length | |
| ) | |
| if blog_post and not st.session_state.cancel_generation: | |
| st.session_state.blog_post = blog_post | |
| st.success("Blog post generated successfully!") | |
| elif st.session_state.cancel_generation: | |
| st.warning("Blog post generation was cancelled.") | |
| else: | |
| st.error("Failed to generate the blog post. Please try again later.") | |
| except Exception as e: | |
| st.error(f"An unexpected error occurred: {str(e)}") | |
| finally: | |
| st.session_state.processing = False | |
| # Display results | |
| if st.session_state.blog_post: | |
| st.markdown(st.session_state.blog_post) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.download_button( | |
| "Download Blog Post", | |
| st.session_state.blog_post, | |
| "blog_post.md", | |
| "text/markdown" | |
| ): | |
| st.success("Blog post downloaded successfully!") | |
| with col2: | |
| if st.button("Reset"): | |
| st.session_state.blog_post = None | |
| st.experimental_rerun() | |
| if __name__ == "__main__": | |
| main() | |
| # Sidebar with creator information | |
| st.sidebar.markdown("---") | |
| st.sidebar.title("About the Creator") | |
| st.sidebar.info(""" | |
| Designed by Richardson Gunde π¨ | |
| This advanced application uses AI to generate a single, comprehensive blog post based on long-form content from YouTube videos or user input. | |
| π [LinkedIn](https://www.linkedin.com/in/richardson-gunde) | |
| π§ [Email](mailto:gunderichardson@gmail.com) | |
| """) | |
| st.markdown(""" | |
| --- | |
| :green[This advanced app leverages the power of Google's Gemini AI to generate a single, detailed, SEO-optimized long-form blog post from YouTube videos or custom text. | |
| It handles extensive content while ensuring a cohesive output.] | |
| """) |