| import streamlit as st |
| import google.generativeai as genai |
| import tempfile |
| import os |
| import time |
| import json |
| from typing import Optional |
| import pandas as pd |
| import logging |
| from database import insert_analysis_result |
| from dotenv import load_dotenv |
|
|
| load_dotenv() |
|
|
| |
| GEMINI_API_KEY = os.getenv("GEMINI_KEY") |
|
|
| |
| st.set_page_config( |
| page_title="Video Analyser and Script Generator", |
| page_icon="🎥", |
| layout="wide", |
| initial_sidebar_state="expanded" |
| ) |
|
|
| |
| logging.basicConfig( |
| level=logging.DEBUG, |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", |
| handlers=[ |
| logging.StreamHandler() |
| ] |
| ) |
| logger = logging.getLogger(__name__) |
|
|
| def configure_gemini(): |
| """Configure Gemini API with backend key""" |
| logger.info("Starting Gemini API configuration...") |
| |
| if not GEMINI_API_KEY: |
| error_msg = "GEMINI_KEY not found in environment variables" |
| logger.error(error_msg) |
| st.error(error_msg) |
| return False |
| |
| logger.info(f"API Key found, length: {len(GEMINI_API_KEY)}") |
| logger.debug(f"API Key starts with: {GEMINI_API_KEY[:10]}..." if len(GEMINI_API_KEY) > 10 else "API Key too short") |
| |
| try: |
| genai.configure(api_key=GEMINI_API_KEY) |
| logger.info("Gemini API configured successfully") |
| |
| |
| logger.info("Testing API connection...") |
| models = list(genai.list_models()) |
| logger.info(f"Available models: {[model.name for model in models]}") |
| |
| return True |
| except Exception as e: |
| error_msg = f"Failed to configure Gemini API: {str(e)}" |
| logger.error(error_msg, exc_info=True) |
| st.error(error_msg) |
| return False |
|
|
| |
| SYSTEM_PROMPT = f"""{os.getenv("SYS_PROMPT")}""" |
| logger.info(f"System prompt loaded, length: {len(SYSTEM_PROMPT) if SYSTEM_PROMPT else 0}") |
|
|
| def analyze_video_and_generate_script( |
| video_bytes, |
| video_name, |
| offer_details: str = "", |
| target_audience: str = "", |
| specific_hooks: str = "", |
| additional_context: str = "" |
| ): |
| """ |
| Analyze video and generate direct response script variations |
| """ |
| logger.info(f"Starting video analysis for: {video_name}") |
| logger.info(f"Video size: {len(video_bytes)} bytes") |
| |
| try: |
| |
| logger.info("Creating temporary file...") |
| with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(video_name)[1]) as tmp_file: |
| tmp_file.write(video_bytes) |
| tmp_file_path = tmp_file.name |
| |
| logger.info(f"Temporary file created: {tmp_file_path}") |
| logger.info(f"File size on disk: {os.path.getsize(tmp_file_path)} bytes") |
| |
| |
| logger.info("Configuring Gemini API...") |
| if not configure_gemini(): |
| logger.error("Gemini configuration failed") |
| return None |
| |
| |
| upload_progress = st.progress(0) |
| upload_status = st.empty() |
| |
| upload_status.text("Uploading video to Google AI...") |
| upload_progress.progress(20) |
| logger.info("Starting file upload to Gemini...") |
| |
| try: |
| |
| video_file_obj = genai.upload_file(tmp_file_path) |
| logger.info(f"File uploaded successfully. File URI: {video_file_obj.uri}") |
| logger.info(f"File state: {video_file_obj.state.name}") |
| upload_progress.progress(40) |
| |
| except Exception as upload_error: |
| error_msg = f"File upload failed: {str(upload_error)}" |
| logger.error(error_msg, exc_info=True) |
| upload_status.error(error_msg) |
| return None |
| |
| upload_status.text("Processing video...") |
| logger.info("Waiting for video processing...") |
| |
| processing_attempts = 0 |
| max_processing_attempts = 30 |
| |
| while video_file_obj.state.name == "PROCESSING": |
| processing_attempts += 1 |
| logger.debug(f"Processing attempt {processing_attempts}/{max_processing_attempts}") |
| |
| if processing_attempts > max_processing_attempts: |
| error_msg = "Video processing timed out after 1 minute" |
| logger.error(error_msg) |
| upload_status.error(error_msg) |
| return None |
| |
| time.sleep(2) |
| try: |
| video_file_obj = genai.get_file(video_file_obj.name) |
| logger.debug(f"Processing state: {video_file_obj.state.name}") |
| except Exception as get_file_error: |
| logger.error(f"Error checking file status: {str(get_file_error)}", exc_info=True) |
| break |
| |
| upload_progress.progress(40 + (processing_attempts * 20 // max_processing_attempts)) |
| |
| logger.info(f"Final file state: {video_file_obj.state.name}") |
| |
| if video_file_obj.state.name == "FAILED": |
| error_msg = "Google AI file processing failed. Please try another video." |
| logger.error(error_msg) |
| upload_status.error(error_msg) |
| return None |
| |
| if video_file_obj.state.name != "ACTIVE": |
| error_msg = f"Unexpected file state: {video_file_obj.state.name}" |
| logger.error(error_msg) |
| upload_status.error(error_msg) |
| return None |
| |
| upload_progress.progress(80) |
| upload_status.text("Generating script variations...") |
| logger.info("Starting content generation...") |
| |
| |
| user_prompt = f"""Analyze this reference video and generate 3 high-converting direct response video script variations with detailed timestamp-based improvements. |
| |
| IMPORTANT CONTEXT TO FOLLOW WHEN CREATING OUTPUT: |
| - Offer Details: {offer_details} |
| - Target Audience: {target_audience} |
| - Specific Hooks: {specific_hooks} |
| |
| ADDITIONAL CONTEXT (MANDATORY TO FOLLOW): |
| {additional_context} |
| |
| You must reflect this additional context in: |
| - The script tone, CTA, visuals |
| - Compliance or branding constraints |
| - Any assumptions about audience or product |
| |
| Failure to include this will be considered incomplete. |
| |
| Please provide a comprehensive analysis including: |
| |
| 1. DETAILED VIDEO ANALYSIS with timestamp-based metrics: |
| - Break down the video into 5-10 second segments |
| - Rate each segment's effectiveness (1-10 scale) |
| - Identify specific elements (hook, transition, proof, CTA, etc.) |
| |
| 2. TIMESTAMP-BASED IMPROVEMENTS: |
| - Specific recommendations for each time segment |
| - Priority level for each improvement |
| - Expected impact of implementing changes |
| |
| 3. SCRIPT VARIATIONS: |
| - Create 2-3 complete script variations |
| - Each with timestamp-by-timestamp breakdown |
| - Different psychological triggers and approaches |
| |
| IMPORTANT: Return only valid JSON in the exact format specified in the system prompt. Analyze the video second-by-second for maximum detail.""" |
|
|
| logger.info(f"User prompt length: {len(user_prompt)}") |
| logger.info(f"System prompt length: {len(SYSTEM_PROMPT) if SYSTEM_PROMPT else 0}") |
|
|
| |
| try: |
| logger.info("Creating GenerativeModel instance...") |
| model = genai.GenerativeModel("gemini-2.0-flash-exp") |
| logger.info("Model created successfully") |
| |
| logger.info("Generating content with video and prompts...") |
| full_prompt = user_prompt + "\n\n" + (SYSTEM_PROMPT or "") |
| logger.debug(f"Full prompt length: {len(full_prompt)}") |
| |
| response = model.generate_content([video_file_obj, full_prompt]) |
| logger.info("Content generation completed successfully") |
| logger.debug(f"Response text length: {len(response.text) if hasattr(response, 'text') else 'No text attribute'}") |
| |
| except Exception as generation_error: |
| error_msg = f"Error generating content with Gemini: {str(generation_error)}" |
| logger.error(error_msg, exc_info=True) |
| upload_status.error(error_msg) |
| return None |
| |
| upload_progress.progress(100) |
| upload_status.success("Analysis complete!") |
| logger.info("Video analysis completed successfully") |
| |
| |
| try: |
| os.unlink(tmp_file_path) |
| logger.info(f"Temporary file deleted: {tmp_file_path}") |
| except Exception as cleanup_error: |
| logger.warning(f"Failed to delete temporary file: {str(cleanup_error)}") |
| |
| |
| logger.info("Parsing JSON response...") |
| try: |
| if not hasattr(response, 'text'): |
| error_msg = "Response object has no text attribute" |
| logger.error(error_msg) |
| st.error(error_msg) |
| return None |
| |
| response_text = response.text.strip() |
| logger.debug(f"Raw response text preview: {response_text[:500]}...") |
| |
| if response_text.startswith('```json'): |
| response_text = response_text[7:-3] |
| logger.debug("Removed json code block markers") |
| elif response_text.startswith('```'): |
| response_text = response_text[3:-3] |
| logger.debug("Removed generic code block markers") |
| |
| logger.debug(f"Cleaned response text preview: {response_text[:500]}...") |
| |
| json_response = json.loads(response_text) |
| logger.info("JSON parsing successful") |
| logger.debug(f"JSON keys: {list(json_response.keys()) if isinstance(json_response, dict) else 'Not a dict'}") |
| |
| return json_response |
| |
| except json.JSONDecodeError as json_error: |
| error_msg = f"Error parsing AI response as JSON: {str(json_error)}" |
| logger.error(error_msg) |
| logger.error(f"Response text that failed to parse: {response_text[:1000]}...") |
| st.error(error_msg) |
| st.text_area("Raw Response (for debugging):", response_text, height=200) |
| return None |
| |
| except Exception as e: |
| error_msg = f"Unexpected error processing video: {str(e)}" |
| logger.error(error_msg, exc_info=True) |
| st.error(error_msg) |
| return None |
|
|
| def display_script_variations(json_data): |
| """Display script variations in formatted tables""" |
| logger.info("Displaying script variations...") |
| |
| if not json_data or "script_variations" not in json_data: |
| error_msg = "No script variations found in the response" |
| logger.error(error_msg) |
| logger.debug(f"JSON data keys: {list(json_data.keys()) if isinstance(json_data, dict) else 'Not a dict'}") |
| st.error(error_msg) |
| return |
| |
| try: |
| variations = json_data["script_variations"] |
| logger.info(f"Found {len(variations)} script variations") |
| |
| for i, variation in enumerate(variations, 1): |
| variation_name = variation.get("variation_name", f"Variation {i}") |
| logger.debug(f"Processing variation {i}: {variation_name}") |
|
|
| st.markdown(f"### Variation {i}: {variation_name}") |
|
|
| |
| script_data = variation.get("script_table") |
| if not script_data: |
| warning_msg = f"No script data for {variation_name}" |
| logger.warning(warning_msg) |
| st.warning(warning_msg) |
| continue |
|
|
| logger.debug(f"Script data for {variation_name}: {len(script_data)} rows") |
| |
| df = pd.DataFrame(script_data) |
|
|
| |
| df = df.rename(columns={ |
| 'timestamp': 'Timestamp', |
| 'script_voiceover': 'Script / Voiceover', |
| 'visual_direction': 'Visual Direction', |
| 'psychological_trigger': 'Psychological Trigger', |
| 'cta_action': 'CTA / Action' |
| }) |
|
|
| st.table(df) |
| st.markdown("---") |
| |
| logger.info("Script variations displayed successfully") |
| |
| except Exception as e: |
| error_msg = f"Error displaying script variations: {str(e)}" |
| logger.error(error_msg, exc_info=True) |
| st.error(error_msg) |
|
|
| def display_video_analysis(json_data): |
| """Display video analysis in tabular format""" |
| logger.info("Displaying video analysis...") |
| |
| if not json_data or "video_analysis" not in json_data: |
| error_msg = "No video analysis found in the response" |
| logger.error(error_msg) |
| st.error(error_msg) |
| return |
| |
| try: |
| analysis = json_data["video_analysis"] |
| logger.debug(f"Video analysis type: {type(analysis)}") |
|
|
| |
| video_metrics = [] |
| if isinstance(analysis, dict): |
| col1, col2 = st.columns(2) |
|
|
| with col1: |
| st.subheader("Effectiveness Factors") |
| effectiveness = analysis.get('effectiveness_factors', 'N/A') |
| st.write(effectiveness) |
| logger.debug(f"Effectiveness factors: {effectiveness}") |
|
|
| st.subheader("Target Audience") |
| audience = analysis.get('target_audience', 'N/A') |
| st.write(audience) |
| logger.debug(f"Target audience: {audience}") |
|
|
| with col2: |
| st.subheader("Psychological Triggers") |
| triggers = analysis.get('psychological_triggers', 'N/A') |
| st.write(triggers) |
| logger.debug(f"Psychological triggers: {triggers}") |
|
|
| video_metrics = analysis.get("video_metrics", []) |
| logger.debug(f"Video metrics count: {len(video_metrics)}") |
|
|
| else: |
| warning_msg = "Unexpected format in video_analysis. Skipping metadata." |
| logger.warning(warning_msg) |
| st.warning(warning_msg) |
| if isinstance(analysis, list): |
| video_metrics = analysis |
|
|
| if video_metrics: |
| logger.info(f"Processing {len(video_metrics)} video metrics") |
| metrics_df = pd.DataFrame(video_metrics) |
| |
| |
| column_mapping = { |
| 'timestamp': 'Timestamp', |
| 'element': 'Element', |
| 'current_approach': 'Current Approach', |
| 'effectiveness_score': 'Score', |
| 'notes': 'Analysis Notes' |
| } |
| |
| metrics_df = metrics_df.rename(columns=column_mapping) |
| logger.debug(f"Metrics dataframe columns: {list(metrics_df.columns)}") |
| |
| st.dataframe( |
| metrics_df, |
| use_container_width=True, |
| hide_index=True, |
| column_config={ |
| "Timestamp": st.column_config.TextColumn(width="small"), |
| "Element": st.column_config.TextColumn(width="medium"), |
| "Current Approach": st.column_config.TextColumn(width="large"), |
| "Score": st.column_config.TextColumn(width="small"), |
| "Analysis Notes": st.column_config.TextColumn(width="large") |
| } |
| ) |
| else: |
| warning_msg = "No detailed video metrics available" |
| logger.warning(warning_msg) |
| st.warning(warning_msg) |
| |
| logger.info("Video analysis displayed successfully") |
| |
| except Exception as e: |
| error_msg = f"Error displaying video analysis: {str(e)}" |
| logger.error(error_msg, exc_info=True) |
| st.error(error_msg) |
|
|
| def display_timestamp_improvements(json_data): |
| """Display timestamp-based improvements in tabular format""" |
| logger.info("Displaying timestamp improvements...") |
| |
| improvements = json_data.get("timestamp_improvements") |
|
|
| if improvements is None: |
| error_msg = "No timestamp improvements found in the response" |
| logger.error(error_msg) |
| st.error(error_msg) |
| return |
|
|
| if not improvements: |
| warning_msg = "No timestamp improvements available" |
| logger.warning(warning_msg) |
| st.warning(warning_msg) |
| return |
|
|
| try: |
| st.subheader("Timestamp-by-Timestamp Improvement Recommendations") |
| logger.info(f"Processing {len(improvements)} improvement recommendations") |
| |
| improvements_df = pd.DataFrame(improvements) |
| |
| |
| column_mapping = { |
| 'timestamp': 'Timestamp', |
| 'current_element': 'Current Element', |
| 'improvement_type': 'Improvement Type', |
| 'recommended_change': 'Recommended Change', |
| 'expected_impact': 'Expected Impact', |
| 'priority': 'Priority' |
| } |
| |
| improvements_df = improvements_df.rename(columns=column_mapping) |
| logger.debug(f"Improvements dataframe columns: {list(improvements_df.columns)}") |
| |
| |
| def color_priority(val): |
| if val == 'High': |
| return 'background-color: #ffcccb' |
| elif val == 'Medium': |
| return 'background-color: #ffffcc' |
| elif val == 'Low': |
| return 'background-color: #ccffcc' |
| return '' |
| |
| styled_df = improvements_df.style.applymap(color_priority, subset=['Priority']) |
| |
| st.dataframe( |
| styled_df, |
| use_container_width=True, |
| hide_index=True, |
| column_config={ |
| "Timestamp": st.column_config.TextColumn(width="small"), |
| "Current Element": st.column_config.TextColumn(width="medium"), |
| "Improvement Type": st.column_config.TextColumn(width="medium"), |
| "Recommended Change": st.column_config.TextColumn(width="large"), |
| "Expected Impact": st.column_config.TextColumn(width="medium"), |
| "Priority": st.column_config.TextColumn(width="small") |
| } |
| ) |
| |
| logger.info("Timestamp improvements displayed successfully") |
| |
| except Exception as e: |
| error_msg = f"Error displaying timestamp improvements: {str(e)}" |
| logger.error(error_msg, exc_info=True) |
| st.error(error_msg) |
|
|
| def create_csv_download(json_data): |
| """Create CSV content with all scripts combined""" |
| logger.info("Creating CSV download...") |
| |
| try: |
| all_scripts_data = [] |
| |
| |
| for i, variation in enumerate(json_data.get("script_variations", []), 1): |
| variation_name = variation.get("variation_name", f"Variation {i}") |
| logger.debug(f"Processing variation for CSV: {variation_name}") |
| |
| for row in variation.get("script_table", []): |
| script_row = { |
| 'Variation': variation_name, |
| 'Timestamp': row.get('timestamp', ''), |
| 'Script_Voiceover': row.get('script_voiceover', ''), |
| 'Visual_Direction': row.get('visual_direction', ''), |
| 'Psychological_Trigger': row.get('psychological_trigger', ''), |
| 'CTA_Action': row.get('cta_action', '') |
| } |
| all_scripts_data.append(script_row) |
| |
| |
| if all_scripts_data: |
| df = pd.DataFrame(all_scripts_data) |
| csv_content = df.to_csv(index=False) |
| logger.info(f"CSV created successfully with {len(all_scripts_data)} rows") |
| return csv_content |
| else: |
| logger.warning("No script data available for CSV") |
| return "No script data available" |
| |
| except Exception as e: |
| error_msg = f"Error creating CSV: {str(e)}" |
| logger.error(error_msg, exc_info=True) |
| return f"Error creating CSV: {error_msg}" |
|
|
| def check_token(user_token): |
| logger.info("Checking access token...") |
| |
| ACCESS_TOKEN = os.getenv("ACCESS_TOKEN") |
| if not ACCESS_TOKEN: |
| error_msg = "ACCESS_TOKEN not set in environment." |
| logger.critical(error_msg) |
| return False, "Server error: Access token not configured." |
| |
| if user_token == ACCESS_TOKEN: |
| logger.info("Access token validated successfully.") |
| return True, "" |
| |
| logger.warning("Invalid access token attempt.") |
| return False, "Invalid token." |
|
|
| def main(): |
| """Main application function""" |
| logger.info("Starting main application...") |
|
|
| if "authenticated" not in st.session_state: |
| st.session_state["authenticated"] = False |
| logger.debug("Authentication state initialized") |
|
|
| if not st.session_state["authenticated"]: |
| logger.info("User not authenticated, showing login screen") |
| st.markdown("## Access Required") |
| token_input = st.text_input("Enter Access Token", type="password") |
| if st.button("Unlock App"): |
| ok, error_msg = check_token(token_input) |
| if ok: |
| st.session_state["authenticated"] = True |
| logger.info("User authenticated successfully") |
| st.rerun() |
| else: |
| logger.warning(f"Authentication failed: {error_msg}") |
| st.error(error_msg) |
| return |
|
|
| |
| if st.sidebar.button("🔧 Test API Connection"): |
| logger.info("Testing API connection...") |
| try: |
| genai.configure(api_key=GEMINI_API_KEY) |
| models = list(genai.list_models()) |
| st.sidebar.success(f"✅ API Working! Found {len(models)} models") |
| logger.info(f"API test successful, found {len(models)} models") |
| for model in models[:3]: |
| st.sidebar.text(f"• {model.name}") |
| except Exception as e: |
| error_msg = f"❌ API Test Failed: {str(e)}" |
| st.sidebar.error(error_msg) |
| logger.error(f"API test failed: {str(e)}", exc_info=True) |
|
|
| |
| if st.session_state["authenticated"]: |
| logger.info("User authenticated, showing main interface") |
|
|
| selected_tab = st.sidebar.radio("Select Mode", ["Script Generator", "History"]) |
| logger.debug(f"Selected tab: {selected_tab}") |
|
|
| |
| if selected_tab == "Script Generator": |
| logger.info("Script Generator mode selected") |
| |
| with st.expander("How to Use This Tool", expanded=False): |
| st.markdown(""" |
| ### Upload Guidelines: |
| - **Best videos to analyze**: Already profitable Facebook/TikTok ads in your niche |
| - **Video length**: 30–90 seconds work best for analysis |
| - **Quality**: Clear audio and visuals help with better analysis |
| |
| ### Context Tips: |
| - **Offer details**: Be specific about your main promise and mechanism |
| - **Audience**: Include demographics, pain points, and desires |
| - **Hooks**: Mention any specific angles that have worked for you |
| |
| ### Script Optimization: |
| - Generated scripts focus on stopping scroll and driving clicks |
| - Each variation tests different psychological triggers |
| - Use the timestamp format for precise video production |
| - Test multiple variations to find your best performer |
| """) |
| st.subheader("Input Configuration") |
|
|
| uploaded_video = st.file_uploader( |
| "Upload Reference Video", |
| type=['mp4', 'mov', 'avi', 'mkv'], |
| help="Upload a profitable ad video to analyze and create variations from" |
| ) |
| |
| if uploaded_video is not None: |
| logger.info(f"Video uploaded: {uploaded_video.name}, size: {uploaded_video.size} bytes") |
| else: |
| st.info("Please upload a reference video to begin analysis.") |
|
|
| st.subheader("Additional Context (Optional)") |
| |
| offer_details = st.text_area( |
| "Offer Details", |
| placeholder="e.g., Solar installation with $0 down payment...", |
| height=80, |
| help="Describe the product/service and main promise" |
| ) |
| |
| target_audience = st.text_area( |
| "Target Audience", |
| placeholder="e.g., 40+ homeowners with high electricity bills...", |
| height=80, |
| help="Describe the ideal customer demographics and pain points" |
| ) |
| |
| specific_hooks = st.text_area( |
| "Specific Hooks to Test", |
| placeholder="e.g., Government rebate angle, celebrity endorsement...", |
| height=80, |
| help="Any specific angles or hooks you want to incorporate" |
| ) |
| |
| additional_context = st.text_area( |
| "Additional Context", |
| placeholder="Any other relevant information...", |
| height=100, |
| help="Compliance requirements, brand guidelines, or other notes" |
| ) |
|
|
| generate_button = st.button("Generate Script Variations", use_container_width=True) |
|
|
| if "analysis_results" in st.session_state and st.session_state["analysis_results"]: |
| if st.button("Clear Results", use_container_width=True): |
| del st.session_state["analysis_results"] |
| logger.info("Analysis results cleared") |
| st.rerun() |
|
|
| |
| if uploaded_video and generate_button: |
| logger.info("Starting video analysis process...") |
| |
| with st.spinner("Analyzing video and generating scripts..."): |
| video_bytes = uploaded_video.read() |
| uploaded_video.seek(0) |
|
|
| json_response = analyze_video_and_generate_script( |
| video_bytes, |
| uploaded_video.name, |
| offer_details, |
| target_audience, |
| specific_hooks, |
| additional_context |
| ) |
|
|
| if json_response: |
| logger.info("Analysis completed successfully, saving to database...") |
| try: |
| insert_analysis_result( |
| video_name=uploaded_video.name, |
| offer_details=offer_details, |
| target_audience=target_audience, |
| specific_hook=specific_hooks, |
| additional_context=additional_context, |
| response=json_response |
| ) |
| logger.info("Results saved to database") |
| except Exception as db_error: |
| logger.error(f"Failed to save to database: {str(db_error)}", exc_info=True) |
| st.warning("Analysis completed but failed to save to database") |
| |
| st.session_state["analysis_results"] = json_response |
| else: |
| logger.error("Analysis failed, no response received") |
|
|
| if "analysis_results" in st.session_state: |
| logger.info("Displaying analysis results...") |
| json_response = st.session_state["analysis_results"] |
|
|
| tab1, tab2, tab3 = st.tabs(["Script Variations", "Video Analysis", "Improvement Recommendations"]) |
| |
| with tab1: |
| display_script_variations(json_response) |
| csv_content = create_csv_download(json_response) |
| st.download_button("Download All Scripts (CSV)", data=csv_content, |
| file_name="video_script_variations.csv", mime="text/csv") |
| with tab2: |
| display_video_analysis(json_response) |
| with tab3: |
| display_timestamp_improvements(json_response) |
|
|
| |
| elif selected_tab == "History": |
| logger.info("History mode selected") |
| |
| try: |
| from database import get_all_results |
| history_items = get_all_results(limit=20) |
| logger.info(f"Retrieved {len(history_items) if history_items else 0} history items") |
|
|
| if history_items: |
| video_titles = [ |
| f"{item['video_name']} ({item['created_at'].strftime('%Y-%m-%d %H:%M')})" |
| for item in history_items |
| ] |
|
|
| selected = st.sidebar.radio("History Items", video_titles, index=0) |
| selected_index = video_titles.index(selected) |
| selected_data = history_items[selected_index] |
| |
| logger.info(f"Selected history item: {selected_data['video_name']}") |
|
|
| st.subheader(f"Analysis for: {selected_data['video_name']}") |
| json_response = selected_data.get("response") |
|
|
| if json_response: |
| tab1, tab2, tab3 = st.tabs(["Script Variations", "Video Analysis", "Improvement Recommendations"]) |
|
|
| with tab1: |
| display_script_variations(json_response) |
| with tab2: |
| display_video_analysis(json_response) |
| with tab3: |
| display_timestamp_improvements(json_response) |
| else: |
| warning_msg = "No valid response data for this analysis." |
| logger.warning(warning_msg) |
| st.warning(warning_msg) |
| else: |
| logger.info("No history items found") |
| st.sidebar.info("No saved analyses found.") |
| st.info("No saved history available.") |
| |
| except Exception as history_error: |
| error_msg = f"Error loading history: {str(history_error)}" |
| logger.error(error_msg, exc_info=True) |
| st.error(error_msg) |
|
|
| if __name__ == "__main__": |
| try: |
| logger.info("=" * 50) |
| logger.info("LAUNCHING VIDEO ANALYZER APPLICATION") |
| logger.info("=" * 50) |
| main() |
| except Exception as e: |
| logger.exception("CRITICAL ERROR: Unhandled error during app launch") |
| st.error(f"Critical application error: {str(e)}") |
| st.error("Please check the logs for more details.") |