diff --git a/.env.example b/.env.example index ecffb9d..7955159 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,10 @@ # TwelveLabs API Configuration # Get your API key from: https://playground.twelvelabs.io/dashboard/api-key -API_KEY=your_twelvelabs_api_key_here +API_KEY=--- # Get your INDEX_ID from: https://playground.twelvelabs.io/indexes/{index_id} # Create an index first at: https://playground.twelvelabs.io/indexes -INDEX_ID=your_index_id_here \ No newline at end of file +INDEX_ID=--- + +AUTH0_DOMAIN=--- +AUTH0_CLIENT_ID=--- diff --git a/ENHANCEMENT_SUMMARY.md b/ENHANCEMENT_SUMMARY.md deleted file mode 100644 index 1f2835c..0000000 --- a/ENHANCEMENT_SUMMARY.md +++ /dev/null @@ -1,194 +0,0 @@ -# Enhanced TwelveLabs Video Q&A System - Feature Summary - -## 🚀 Overview - -Your TwelveLabs YouTube Chapter Timestamp Generator has been significantly enhanced with rich content analysis capabilities. The Q&A interface now provides comprehensive video understanding beyond simple timestamps. - -## ✨ New Features Added - -### 1. **Enhanced Content Analysis Functions** (utils.py) - -#### `generate_summary(client, video_id, prompt=None, temperature=0.3)` - -- Creates concise video summaries using TwelveLabs summarize API -- Supports custom prompts for targeted summaries -- Returns structured summary data with metadata - -#### `generate_chapters(client, video_id, prompt=None, temperature=0.3)` - -- Generates chronological chapters with timestamps and headlines -- Provides detailed chapter summaries -- Returns array of chapter objects with start/end times - -#### `generate_highlights(client, video_id, prompt=None, temperature=0.3)` - -- Extracts most significant events with timestamps -- Identifies key moments in videos -- Returns highlights with precise timing information - -#### `generate_open_analysis(client, video_id, prompt, temperature=0.3, streaming=False)` - -- Performs custom analysis based on your specific prompts -- Supports both streaming and non-streaming responses -- Enables detailed content examination for any purpose - -#### `create_contextual_snippet_analysis(client, video_id, start_time, end_time, query)` - -- Analyzes specific video segments in context of search queries -- Provides detailed explanations of segment content -- Integrates visual and audio analysis for comprehensive understanding - -### 2. **Enhanced Q&A Results Formatting** - -#### `format_qa_results()` - Enhanced Version - -- **Rich Analysis Mode**: Includes detailed segment analysis for each result -- **Basic Mode**: Fast search with content previews -- **Multi-video Support**: Organizes results by video source -- **Contextual Analysis**: Uses search query to guide segment analysis - -#### `format_qa_results_with_summary()` - New Comprehensive Format - -- Includes video summary for overall context -- Shows key highlights before detailed results -- Provides comprehensive analysis for each segment -- Perfect for in-depth content exploration - -### 3. **Enhanced Streamlit Interface** (app.py) - -#### Enhanced Search Options - -- **Analysis Type Selection**: - - "Standard Search": Fast search with basic content preview - - "Enhanced Analysis": Detailed analysis of each segment - - "With Video Summary": Full video context + detailed segments - -#### New Video Analysis Section - -- **One-Click Analysis Buttons**: - - 📝 Generate Summary - - 📑 Generate Chapters - - ✨ Generate Highlights -- **Custom Analysis Prompt**: Enter any analysis request -- **Real-time Results**: All analysis appears immediately in the interface - -#### Enhanced Search Interface - -- **Improved UI**: Better organization and visual hierarchy -- **Analysis Mode Indicators**: Clear explanation of each analysis type -- **Enhanced Error Handling**: Better feedback for users - -## đŸŽ¯ Use Cases & Examples - -### Content Summary & Overview - -```python -# Get comprehensive video summary -summary = generate_summary(client, video_id, - prompt="Summarize this video for a business audience") -``` - -### Chapter-based Navigation - -```python -# Generate detailed chapters for long content -chapters = generate_chapters(client, video_id, - prompt="Create chapters focusing on key learning objectives") -``` - -### Key Moment Identification - -```python -# Extract important highlights -highlights = generate_highlights(client, video_id, - prompt="Find the most impactful moments for marketing") -``` - -### Custom Analysis - -```python -# Perform any analysis task -analysis = generate_open_analysis(client, video_id, - "Analyze the emotional tone and identify key themes") -``` - -### Smart Q&A Search - -Users can now search and get: - -- **Relevant segments** with precise timestamps -- **Detailed content analysis** for each segment -- **Visual and audio insights** from TwelveLabs AI -- **Contextual explanations** of why segments match the query - -## 🔧 Technical Improvements - -### API Integration - -- Full compatibility with TwelveLabs v1.3 SDK -- Proper error handling and fallbacks -- Efficient API usage with appropriate timeouts - -### User Experience - -- **Progressive Enhancement**: Features work even if analysis fails -- **Clear Feedback**: Users understand what each option does -- **Flexible Options**: Multiple analysis modes for different needs - -### Performance - -- **Smart Caching**: Avoids redundant API calls -- **Streaming Support**: Real-time analysis for long content -- **Efficient Processing**: Optimized for responsiveness - -## đŸšĻ Getting Started - -### For Users: - -1. **Upload or select a video** in the interface -2. **Navigate to Q&A tab** -3. **Choose analysis type**: Standard, Enhanced, or With Summary -4. **Ask questions** and get rich, detailed responses -5. **Use analysis buttons** for immediate insights - -### For Developers: - -```python -from utils import generate_summary, generate_chapters, generate_highlights - -# Basic usage -client = TwelveLabs(api_key=API_KEY) -summary = generate_summary(client, video_id) -chapters = generate_chapters(client, video_id) -highlights = generate_highlights(client, video_id) -``` - -## 🎉 Benefits - -### For Content Creators - -- **Quick Insights**: Understand video content instantly -- **Better Timestamps**: More accurate and meaningful chapters -- **Content Optimization**: Identify key moments and themes - -### For Educators - -- **Learning Objectives**: Extract key educational content -- **Chapter Creation**: Automatic course structuring -- **Student Q&A**: Answer questions about video content - -### For Businesses - -- **Meeting Analysis**: Summarize recorded meetings -- **Training Content**: Extract key points from training videos -- **Content Strategy**: Understand video performance and themes - -## 🔮 What's New in the Interface - -1. **Enhanced Search Results**: Rich content analysis with each search result -2. **Video Analysis Section**: One-click analysis tools for any video -3. **Multiple Analysis Modes**: Choose the right level of detail for your needs -4. **Custom Prompts**: Ask any analysis question you want -5. **Better Organization**: Clear separation of features and intuitive navigation - -This enhancement transforms your application from a simple timestamp generator into a comprehensive video understanding platform powered by TwelveLabs' advanced AI capabilities! diff --git a/QA_USAGE_GUIDE.md b/QA_USAGE_GUIDE.md deleted file mode 100644 index a87de5e..0000000 --- a/QA_USAGE_GUIDE.md +++ /dev/null @@ -1,170 +0,0 @@ -# Video Q&A Interface Usage Guide - -## 🤖 What is the Video Q&A Interface? - -The Video Q&A interface allows you to ask questions about your uploaded videos and automatically retrieve relevant video segments as answers. This feature uses TwelveLabs AI to understand your video content and find the most relevant portions. - -## đŸŽ¯ How to Use the Q&A Interface - -### Step 1: Prepare Your Video - -1. **Upload a new video** or **select an existing video** from the first two tabs -2. Wait for the video to be processed (you'll see "Video processed successfully!") -3. Navigate to the **"🤖 Video Q&A"** tab - -### Step 2: Ask Questions - -1. **Enter your question** in the search box. Examples: - - - "What are the main topics discussed?" - - "Show me the introduction" - - "Where is the conclusion?" - - "What products are mentioned?" - - "Find sections about pricing" - - "Show me when they talk about features" - -2. **Select max results** (3, 5, or 10 segments) - -3. **Click "Search Video"** to find relevant segments - -### Step 3: Review Results - -The interface will show: - -- **Confidence Score**: How relevant each segment is (higher = more relevant) -- **Timestamps**: Start and end time of each segment -- **Duration**: Length of each segment -- **Content Preview**: Text preview of what's discussed in that segment - -### Step 4: Create Video Snippets - -1. **Click "Create Video Snippets"** to generate downloadable video clips -2. Wait for processing (progress bar will show status) -3. **Download individual snippets** or view them inline - -## 🔍 Types of Questions You Can Ask - -### Content-Based Questions - -- "What topics are covered?" -- "Where do they discuss [specific topic]?" -- "Show me the main points" - -### Structure-Based Questions - -- "Find the introduction" -- "Where is the conclusion?" -- "Show me the summary section" - -### Object/Person Recognition - -- "When do people appear on screen?" -- "Show me product demonstrations" -- "Find slides or presentations" - -### Keyword-Based Searches - -- "Find mentions of [specific word/phrase]" -- "Where do they talk about pricing?" -- "Show segments about features" - -## 📊 Understanding Results - -### Confidence Scores - -- **90-100%**: Extremely relevant match -- **70-90%**: Highly relevant match -- **50-70%**: Moderately relevant match -- **Below 50%**: Low relevance (may still be useful) - -### Time Segments - -- Results show exact timestamps you can reference -- Segments are automatically trimmed to relevant portions -- Duration shows how long each segment is - -## đŸŽŦ Video Snippet Features - -### Automatic Naming - -- Snippets are named based on your query and timestamps -- Format: `qa_snippet_01_your_query_00:30-01:45.mp4` - -### Download Options - -- Each snippet can be downloaded individually -- MP4 format compatible with most players -- Includes metadata about confidence and timing - -## 💡 Tips for Better Results - -### Write Clear Questions - -- ✅ "Where do they explain the installation process?" -- ❌ "installation" - -### Be Specific - -- ✅ "Show me product pricing information" -- ❌ "money stuff" - -### Use Natural Language - -- ✅ "What are the main benefits discussed?" -- ❌ "benefits main what" - -### Try Different Phrasings - -- If you don't get good results, try rephrasing your question -- Use synonyms or different terms - -## ⚡ Troubleshooting - -### "Video is still being processed" - -- Wait a few minutes after upload -- Video needs to be fully indexed for search - -### "No relevant segments found" - -- Try rephrasing your question -- Use broader or more specific terms -- Check if the content actually exists in the video - -### "Video snippets require streaming URL" - -- Click "Refresh Video URL" in the timestamps section -- Some videos may not support streaming immediately - -### Poor Search Results - -- Ensure your question relates to actual video content -- Try shorter, more focused queries -- Use specific keywords that appear in the video - -## 🔧 Technical Requirements - -- Video must be fully processed and indexed -- Streaming URL must be available for snippet creation -- TwelveLabs index must support search capabilities -- Video should have clear audio/visual content for best results - -## 📈 Advanced Usage - -### Combining with Timestamps - -- Use Q&A to find specific sections -- Use timestamp generator for overall chapter structure -- Create focused segments with Q&A, full chapters with timestamps - -### Multiple Queries - -- Ask several questions to explore different aspects -- Each query creates separate snippets -- Results are ordered by relevance - -### Content Analysis - -- Use Q&A to analyze video content themes -- Identify key discussion points -- Extract specific information or quotes diff --git a/README.md b/README.md index 27901a3..103afd7 100644 --- a/README.md +++ b/README.md @@ -1,295 +1 @@ -# YouTube Chapter Highlight Generator & Video Q&A - -Generate chapter highlight timestamps and ask questions about your YouTube videos using TwelveLabs AI. - -## About - -The YouTube Chapter Highlight Generator is an AI-powered tool that automatically generates chapter timestamps for YouTube videos and provides an intelligent Q&A interface for video content exploration. By analyzing video content using TwelveLabs AI, it identifies key segments, creates timestamps, and allows you to ask questions to retrieve specific video portions. - -This tool helps content creators save time and effort by automatically generating timestamp highlights and providing instant access to specific video content through natural language queries. - -## Features - -### 📹 Core Video Processing - -- **Video Upload & Processing**: Upload videos and process them using TwelveLabs AI -- **Automatic Chapter Generation**: AI-powered analysis to identify key video segments -- **YouTube-Ready Timestamps**: Generate formatted timestamps for YouTube descriptions -- **Video Segmentation**: Create downloadable video segments based on chapters -- **Existing Video Management**: Browse and select from previously uploaded videos - -### 🤖 Video Q&A Interface - -- **Natural Language Queries**: Ask questions about your video in plain English -- **Intelligent Content Search**: Find relevant video segments based on your queries -- **Confidence Scoring**: See how relevant each result is to your question -- **Smart Video Snippets**: Automatically create video clips for relevant segments -- **Multi-modal Search**: Search across visual content, conversations, and text - -### ⚡ Real-time Features - -- **Live Processing Updates**: Real-time status of video processing -- **Instant Search Results**: Quick response to Q&A queries -- **Progress Tracking**: Visual progress bars for all operations - -## Tech Stack - -- **Frontend**: Streamlit -- **AI/ML**: TwelveLabs AI Platform -- **Video Processing**: MoviePy -- **Backend**: Python -- **Environment**: dotenv for configuration - -## Quick Setup - -### 1. Prerequisites - -- Python 3.8 or higher -- TwelveLabs account and API key -- Git - -### 2. Installation - -```bash -# Clone the repository -git clone https://github.com/Hrishikesh332/Twelvelabs-Youtube-Chapter-Timestamp.git -cd Twelvelabs-Youtube-Chapter-Timestamp - -# Install dependencies -pip install -r requirements.txt -``` - -### 3. Configuration - -1. **Get TwelveLabs Credentials**: - - - Sign up at [TwelveLabs Platform](https://playground.twelvelabs.io/) - - Get your API key from the dashboard - - Create an index and note the Index ID - -2. **Create Environment File**: - - ```bash - # Copy the example file - cp .env.example .env - - # Edit the .env file with your credentials - ``` - -3. **Add Your Credentials to `.env`**: - ```env - TWELVE_LABS_API_KEY=your_api_key_here - TWELVE_LABS_INDEX_ID=your_index_id_here - ``` - -### 4. Test Configuration - -```bash -# Run the configuration test -python test_config.py -``` - -This will verify your API credentials and connection. - -### 5. Launch Application - -```bash -# Start the Streamlit app -streamlit run app.py -``` - -The application will open in your browser at `http://localhost:8501`. - -## Usage - -### 📤 Uploading New Videos - -1. **Upload Video**: Use the file uploader to select your video file -2. **Processing**: Wait for the video to be uploaded and processed by TwelveLabs -3. **Generate Timestamps**: The AI will automatically analyze the video and generate chapter timestamps -4. **Copy Timestamps**: Copy the generated timestamps and paste them into your YouTube video description -5. **Create Segments** (Optional): Generate downloadable video segments for each chapter - -### 📂 Using Existing Videos - -1. **Select from Existing**: Choose "Select from existing videos" option -2. **Browse Videos**: Select from your previously uploaded videos -3. **Generate Timestamps**: Generate new timestamps or view existing ones -4. **Create Segments**: Generate video segments if streaming is available - -### 🤖 Video Q&A Interface - -The new Q&A interface allows you to ask questions about your videos and get relevant video segments as answers! - -#### How to Use Q&A: - -1. **Navigate to Q&A Tab**: Click on "🤖 Video Q&A" after uploading/selecting a video -2. **Ask Questions**: Type natural language questions like: - - "What are the main topics discussed?" - - "Show me the introduction" - - "Where do they talk about pricing?" - - "Find the conclusion section" -3. **Get Results**: View relevant segments with confidence scores and timestamps -4. **Create Snippets**: Generate downloadable video clips for the most relevant parts - -#### Example Queries: - -- **Content Discovery**: "What products are mentioned?" -- **Section Finding**: "Show me the tutorial section" -- **Topic Search**: "Find discussions about features" -- **Structure Navigation**: "Where is the summary?" - -#### Q&A Features: - -- đŸŽ¯ **Confidence Scoring**: See how relevant each result is (0-100%) -- ⏰ **Precise Timestamps**: Exact start/end times for each segment -- 📹 **Auto-Snippet Creation**: Generate video clips from search results -- đŸ’Ŧ **Content Preview**: See text content from each segment -- 🔍 **Multi-modal Search**: Searches visual, audio, and text content - -For detailed Q&A usage instructions, see [QA_USAGE_GUIDE.md](QA_USAGE_GUIDE.md). - -## Troubleshooting - -### Common Issues - -**"The client must be instantiated by either passing in api_key or setting TWELVE_LABS_API_KEY"** - -- Solution: Ensure your `.env` file contains the correct `TWELVE_LABS_API_KEY` - -**"Failed to get video URL: 404 Not Found"** - -- Solution: Wait a few moments after upload for video streaming to be ready, then click "Refresh Video URL" - -**"Video streaming is not available"** - -- Solution: This is normal for some videos. You can still use the timestamps, but video segmentation won't be available - -**Configuration test fails** - -- Solution: Verify your API key and Index ID are correct in the `.env` file - -### Getting Help - -1. Run `python test_config.py` to diagnose configuration issues -2. Check the [TwelveLabs Documentation](https://docs.twelvelabs.io/) -3. Ensure your TwelveLabs index supports video streaming if you need segment creation - -## API Requirements - -- **TwelveLabs SDK**: Version 1.0.2 or higher -- **Python**: 3.8+ -- **Index Type**: Multimodal index with video analysis capabilities - -For the most up-to-date setup instructions, visit the [TwelveLabs Documentation](https://docs.twelvelabs.io/). -border: none; -border-radius: 8px; -text-align: center; -text-decoration: none; -box-shadow: 0 4px 8px rgba(0,0,0,0.2); -transition: background-color 0.3s, box-shadow 0.3s; -"> -YouTube Chapter Timestamp App - - -Demo and Video Explanation - - -[![Watch the video](https://img.youtube.com/vi/z-_PJqjTZmM/hqdefault.jpg)](https://youtu.be/z-_PJqjTZmM) - -## Features - -đŸŽ¯ **Chapter Generation**: Automatically detect and create timestamps with highlights for video chapters. - -🔍 **Content Segmentation**: Identify key points in the video based on its content. - -🚀 **Streamlined Navigation**: Enhance the viewing experience with clickable chapters for easier navigation. - -## Tech Stack - -- **Frontend**: Streamlit -- **Backend**: Python -- **Deployment**: Streamlit Cloud - -## Instructions on running project locally - -To run the YouTube Chapter Highlight Generator locally, follow these steps - - -### Step 1 - Clone the project - -```bash -git clone https://github.com/Hrishikesh332/Twelvelabs-Youtube-Chapter-Timestamp.git -``` - -Step 2 - - -Install dependencies: - -```bash - cd Youtube-Chapter-Timestamp-App - - pip install -r requirements.txt -``` - -Step 3 - - -Set up your Twelve Labs account - - -Create an account on the Twelve Labs Portal -Navigate to the Twelve Labs Playground -Create a new index, select Marengo 2.6 and Pegasus 1.1 - -Step 4 - - -Get your API Key from the [Twelve Labs Dashboard](https://playground.twelvelabs.io/dashboard/api-key) -Find your INDEX_ID in the URL of your created [index](https://playground.twelvelabs.io/indexes/{index_id}) - -Step 5 - - -Configure the application with your API credentials: - -1. Copy the `.env.example` file to `.env`: - -```bash -cp .env.example .env -``` - -2. Edit the `.env` file and add your credentials: - -```env -API_KEY=your_twelvelabs_api_key_here -INDEX_ID=your_index_id_here -``` - -**Important**: Never commit your `.env` file to version control. It's already included in `.gitignore`. - -Step 6 - - -Run the Streamlit application - -```bash - streamlit run app.py -``` - -Step 7 - - -Access the application at: - -```bash - http://localhost:8501/ -``` - -## Usecases - -đŸ“Ŋī¸**YouTube Content Creators**: Automatically generate chapter highlight markers for improved video navigation. - -🎓 **Educational Videos**: Make it easier for students to jump to specific sections of long tutorial videos. - -đŸŽĨ **Content Review**: Easily navigate to important points in long-form video content. - -## Feedback - -If you have any feedback, please reach out to us at **hriskikesh.yadav332@gmail.com** - -## License - -[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +HootHive - HackRice 2025 diff --git a/__pycache__/utils.cpython-311.pyc b/__pycache__/utils.cpython-311.pyc deleted file mode 100644 index 2b4bda1..0000000 Binary files a/__pycache__/utils.cpython-311.pyc and /dev/null differ diff --git a/app.py b/app.py index acaf5be..26ca253 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,19 @@ import streamlit as st import tempfile import os +from datetime import datetime from twelvelabs import TwelveLabs +import streamlit_auth0_component as sac +from auth_config import AUTH0_DOMAIN, AUTH0_CLIENT_ID +import database as db +import logging + +# Initialize logging +logging.basicConfig(level=logging.INFO) + +# Initialize the database +db.init_db() -# Try to import utils and handle configuration errors try: from utils import ( API_KEY, process_video, fetch_existing_videos, @@ -25,63 +35,506 @@ import uuid -# Set up the Streamlit page configuration -st.set_page_config(page_title="YouTube Chapter Timestamp Generator", layout="wide") +def main(): + st.set_page_config( + page_title="đŸŽŦ HootQnA - AI Video Analysis Platform", + page_icon="đŸŽŦ", + layout="wide", + initial_sidebar_state="expanded" + ) + + # Auth0 login + auth_info = sac.login_button( + clientId=AUTH0_CLIENT_ID, + domain=AUTH0_DOMAIN, + ) + + if not auth_info: + st.warning("Please log in to access the application.") + st.stop() + + st.session_state.user_db_id = db.get_or_create_user(auth_info) + + st.sidebar.success(f"Welcome, {auth_info['name']}!") + + with st.sidebar: + sac.logout_button() + + run_app() + +def run_app(): + # Using st.tabs for navigation + tab1, tab2, tab3, tab4 = st.tabs(["Upload Video", "My Videos", "Video Analysis", "Q&A"]) + + with tab1: + upload_and_process_video() + + with tab2: + display_my_videos() + + with tab3: + if 'video_id' in st.session_state and st.session_state.video_id: + display_video_analysis_section() + else: + st.info("Please select a video from the 'My Videos' tab to perform analysis.") + + with tab4: + display_qa_interface() + +def display_my_videos(): + st.header("My Processed Videos") + if 'user_db_id' not in st.session_state: + st.error("User not logged in properly.") + return + + user_videos = db.get_user_videos(st.session_state.user_db_id) + + if not user_videos: + st.info("You haven't processed any videos yet. Go to the 'Upload Video' tab to get started.") + else: + video_options = {f"{v['filename']} ({v['status']})": v['id'] for v in user_videos} + selected_video_display = st.selectbox("Choose a video to analyze", options=video_options.keys()) + + if selected_video_display: + db_video_id = video_options[selected_video_display] + video_data = db.get_video_by_id(db_video_id) + + if video_data and video_data['status'] == 'ready': + st.session_state.video_id = video_data['twelvelabs_video_id'] + st.session_state.db_video_id = db_video_id # Store internal DB id + st.success(f"Selected video: **{video_data['filename']}**") + st.write("You can now generate summaries, chapters, or ask questions about this video in the other tabs.") + elif video_data: + st.warning(f"This video is currently in '{video_data['status']}' state and cannot be analyzed yet.") + + +def log_error(error_type, error_message, context=None, recovery_suggestions=None): + error_entry = { + 'timestamp': st.session_state.get('current_time', 'unknown'), + 'type': error_type, + 'message': error_message, + 'context': context, + 'recovery_suggestions': recovery_suggestions or [] + } + st.session_state.error_log.append(error_entry) + st.session_state.last_error = error_entry + return error_entry + +def display_enhanced_error(error_type, error_message, recovery_suggestions=None): + st.error(f"Error: {error_type} - {error_message}") + + if recovery_suggestions: + with st.expander("Troubleshooting & Recovery Options"): + for i, suggestion in enumerate(recovery_suggestions, 1): + st.write(f"{i}. {suggestion}") + + log_error(error_type, error_message, recovery_suggestions=recovery_suggestions) + +def get_recovery_suggestions(error_type): + suggestions = { + 'upload_error': [ + "Check if the video file is not corrupted", + "Ensure the video format is supported (MP4, AVI, MOV)", + "Verify the video file size is under 1GB", + "Try uploading a smaller video segment first", + "Check your internet connection" + ], + 'api_error': [ + "Check your TwelveLabs API key in the .env file", + "Verify your INDEX_ID is correct", + "Ensure you have sufficient API credits", + "Try refreshing the page", + "Contact support if the issue persists" + ], + 'processing_error': [ + "Wait a few minutes and try again", + "Check if the video is still being processed", + "Try with a shorter video", + "Verify your internet connection", + "Clear browser cache and reload" + ], + 'search_error': [ + "Wait for video indexing to complete", + "Try a different search query", + "Check if the video was uploaded successfully", + "Refresh the page and try again" + ] + } + return suggestions.get(error_type, ["Try refreshing the page", "Contact support if the issue persists"]) + +def format_timestamps_for_youtube(timestamps): + if not timestamps: + return "" + + lines = timestamps.strip().split('\n') + youtube_format = [] + + for line in lines: + if '-' in line: + time_part, title_part = line.split('-', 1) + time_part = time_part.strip() + title_part = title_part.strip() + youtube_format.append(f"{time_part} - {title_part}") + + return '\n'.join(youtube_format) + +def export_to_json(data, filename="export"): + import json + from datetime import datetime + + export_data = { + 'export_timestamp': datetime.now().isoformat(), + 'video_id': st.session_state.get('video_id'), + 'timestamps': st.session_state.get('timestamps'), + 'qa_results': data.get('qa_results', []), + 'chapters': data.get('chapters', []), + 'highlights': data.get('highlights', []) + } + + return json.dumps(export_data, indent=2) + +def export_to_csv(data, filename="export"): + import csv + import io + + output = io.StringIO() + writer = csv.writer(output) + + writer.writerow(['Type', 'Timestamp', 'Title/Query', 'Content', 'Confidence']) + + if st.session_state.get('timestamps'): + for line in st.session_state.timestamps.split('\n'): + if '-' in line: + time_part, title_part = line.split('-', 1) + writer.writerow(['Timestamp', time_part.strip(), title_part.strip(), '', '']) + + for result in data.get('qa_results', []): + writer.writerow([ + 'QA Result', + f"{result.get('start', '')}-{result.get('end', '')}", + result.get('query', ''), + result.get('text', ''), + result.get('confidence', '') + ]) + + return output.getvalue() + +def create_export_button(data, export_type="youtube", label="Export"): + col1, col2, col3 = st.columns([2, 1, 1]) + + with col1: + if export_type == "youtube": + content = format_timestamps_for_youtube(st.session_state.get('timestamps', '')) + st.text_area("YouTube Format (Copy this to your video description):", + value=content, height=100, key=f"export_youtube_{uuid.uuid4()}") + elif export_type == "json": + content = export_to_json(data) + st.text_area("JSON Export:", value=content, height=200, key=f"export_json_{uuid.uuid4()}") + elif export_type == "csv": + content = export_to_csv(data) + st.text_area("CSV Export:", value=content, height=200, key=f"export_csv_{uuid.uuid4()}") + + with col2: + if st.button(f"Copy {export_type.upper()}", key=f"copy_{export_type}_{uuid.uuid4()}"): + st.components.v1.html(f""" + + """, height=0) + st.success(f"Copied {export_type.upper()} format to clipboard!") + + st.session_state.export_history.append({ + 'timestamp': str(datetime.now()), + 'type': export_type, + 'video_id': st.session_state.get('video_id') + }) + + with col3: + if export_type == "json": + st.download_button( + label=f"Download JSON", + data=content, + file_name=f"video_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", + mime="application/json", + key=f"download_json_{uuid.uuid4()}" + ) + elif export_type == "csv": + st.download_button( + label=f"Download CSV", + data=content, + file_name=f"video_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", + mime="text/csv", + key=f"download_csv_{uuid.uuid4()}" + ) + +def log_error(error_type, error_message, context=None, recovery_suggestions=None): + error_entry = { + 'timestamp': st.session_state.get('current_time', 'unknown'), + 'type': error_type, + 'message': error_message, + 'context': context, + 'recovery_suggestions': recovery_suggestions or [] + } + st.session_state.error_log.append(error_entry) + st.session_state.last_error = error_entry + return error_entry + +def display_enhanced_error(error_type, error_message, recovery_suggestions=None): + st.error(f"Error: {error_type} - {error_message}") + + if recovery_suggestions: + with st.expander("Troubleshooting & Recovery Options"): + for i, suggestion in enumerate(recovery_suggestions, 1): + st.write(f"{i}. {suggestion}") + + log_error(error_type, error_message, recovery_suggestions=recovery_suggestions) + +def get_recovery_suggestions(error_type): + suggestions = { + 'upload_error': [ + "Check if the video file is not corrupted", + "Ensure the video format is supported (MP4, AVI, MOV)", + "Verify the video file size is under 1GB", + "Try uploading a smaller video segment first", + "Check your internet connection" + ], + 'api_error': [ + "Check your TwelveLabs API key in the .env file", + "Verify your INDEX_ID is correct", + "Ensure you have sufficient API credits", + "Try refreshing the page", + "Contact support if the issue persists" + ], + 'processing_error': [ + "Wait a few minutes and try again", + "Check if the video is still being processed", + "Try with a shorter video", + "Verify your internet connection", + "Clear browser cache and reload" + ], + 'search_error': [ + "Wait for video indexing to complete", + "Try a different search query", + "Check if the video was uploaded successfully", + "Refresh the page and try again" + ] + } + return suggestions.get(error_type, ["Try refreshing the page", "Contact support if the issue persists"]) + +def format_timestamps_for_youtube(timestamps): + if not timestamps: + return "" + + lines = timestamps.strip().split('\n') + youtube_format = [] + + for line in lines: + if '-' in line: + time_part, title_part = line.split('-', 1) + time_part = time_part.strip() + title_part = title_part.strip() + youtube_format.append(f"{time_part} - {title_part}") + + return '\n'.join(youtube_format) + +def export_to_json(data, filename="export"): + import json + from datetime import datetime + + export_data = { + 'export_timestamp': datetime.now().isoformat(), + 'video_id': st.session_state.get('video_id'), + 'timestamps': st.session_state.get('timestamps'), + 'qa_results': data.get('qa_results', []), + 'chapters': data.get('chapters', []), + 'highlights': data.get('highlights', []) + } + + return json.dumps(export_data, indent=2) + +def export_to_csv(data, filename="export"): + import csv + import io + + output = io.StringIO() + writer = csv.writer(output) + + writer.writerow(['Type', 'Timestamp', 'Title/Query', 'Content', 'Confidence']) + + if st.session_state.get('timestamps'): + for line in st.session_state.timestamps.split('\n'): + if '-' in line: + time_part, title_part = line.split('-', 1) + writer.writerow(['Timestamp', time_part.strip(), title_part.strip(), '', '']) + + for result in data.get('qa_results', []): + writer.writerow([ + 'QA Result', + f"{result.get('start', '')}-{result.get('end', '')}", + result.get('query', ''), + result.get('text', ''), + result.get('confidence', '') + ]) + + return output.getvalue() + +def create_export_button(data, export_type="youtube", label="Export"): + col1, col2, col3 = st.columns([2, 1, 1]) + + with col1: + if export_type == "youtube": + content = format_timestamps_for_youtube(st.session_state.get('timestamps', '')) + st.text_area("YouTube Format (Copy this to your video description):", + value=content, height=100, key=f"export_youtube_{uuid.uuid4()}") + elif export_type == "json": + content = export_to_json(data) + st.text_area("JSON Export:", value=content, height=200, key=f"export_json_{uuid.uuid4()}") + elif export_type == "csv": + content = export_to_csv(data) + st.text_area("CSV Export:", value=content, height=200, key=f"export_csv_{uuid.uuid4()}") + + with col2: + if st.button(f"Copy {export_type.upper()}", key=f"copy_{export_type}_{uuid.uuid4()}"): + st.components.v1.html(f""" + + """, height=0) + st.success(f"Copied {export_type.upper()} format to clipboard!") + + st.session_state.export_history.append({ + 'timestamp': str(datetime.now()), + 'type': export_type, + 'video_id': st.session_state.get('video_id') + }) + + with col3: + if export_type == "json": + st.download_button( + label=f"Download JSON", + data=content, + file_name=f"video_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", + mime="application/json", + key=f"download_json_{uuid.uuid4()}" + ) + elif export_type == "csv": + st.download_button( + label=f"Download CSV", + data=content, + file_name=f"video_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", + mime="text/csv", + key=f"download_csv_{uuid.uuid4()}" + ) + +st.set_page_config( + page_title="đŸŽŦ HootQnA - AI Video Analysis Platform", + page_icon="đŸŽŦ", + layout="wide", + initial_sidebar_state="expanded" +) + +def load_css(file_name): + with open(file_name) as f: + st.markdown(f"", unsafe_allow_html=True) + +load_css("style.css") -# Custom CSS st.markdown(""" - + + """, unsafe_allow_html=True) # Streamlit Page Header -st.markdown("

HootQnA: Chat with Videos ✍🤖

", unsafe_allow_html=True) -st.markdown("

Generate timestamps, create video segments, and ask questions about your videos!

", unsafe_allow_html=True) +st.markdown("

HootQnA AI Video Analysis Platform

", unsafe_allow_html=True) +st.markdown("

Advanced video processing, timestamp generation, and intelligent content analysis

", unsafe_allow_html=True) + +# Keyboard shortcuts help +with st.expander("Keyboard Shortcuts", expanded=False): + col1, col2 = st.columns(2) + with col1: + st.markdown(""" + **Navigation:** + - `Ctrl + U` - Upload tab + - `Ctrl + Q` - Q&A tab + - `Ctrl + S` - Focus search + """) + with col2: + st.markdown(""" + **Actions:** + - `Ctrl + E` - Export timestamps + - `Esc` - Clear search + - `F5` - Refresh page + """) + st.markdown("---") +# Initialize session state with progress persistence +def initialize_session_state(): + """Initialize session state with enhanced progress persistence.""" + # Core video data + if 'timestamps' not in st.session_state: + st.session_state.timestamps = None + if 'video_id' not in st.session_state: + st.session_state.video_id = None + if 'video_segments' not in st.session_state: + st.session_state.video_segments = [] + if 'video_url' not in st.session_state: + st.session_state.video_url = None + if 'qa_results' not in st.session_state: + st.session_state.qa_results = [] + if 'qa_snippets' not in st.session_state: + st.session_state.qa_snippets = [] + if 'chapters_result' not in st.session_state: + st.session_state.chapters_result = None + if 'highlights_result' not in st.session_state: + st.session_state.highlights_result = None + if 'chapter_snippets' not in st.session_state: + st.session_state.chapter_snippets = [] + if 'highlight_snippets' not in st.session_state: + st.session_state.highlight_snippets = [] + + # Progress persistence + if 'processing_status' not in st.session_state: + st.session_state.processing_status = {} + if 'last_upload_info' not in st.session_state: + st.session_state.last_upload_info = None + if 'video_metadata' not in st.session_state: + st.session_state.video_metadata = {} + if 'search_history' not in st.session_state: + st.session_state.search_history = [] + if 'export_history' not in st.session_state: + st.session_state.export_history = [] + + # Batch operations + if 'batch_queue' not in st.session_state: + st.session_state.batch_queue = [] + if 'batch_results' not in st.session_state: + st.session_state.batch_results = {} + if 'batch_processing' not in st.session_state: + st.session_state.batch_processing = False + + # Error tracking + if 'error_log' not in st.session_state: + st.session_state.error_log = [] + if 'last_error' not in st.session_state: + st.session_state.last_error = None + # Initialize session state -if 'timestamps' not in st.session_state: - st.session_state.timestamps = None -if 'video_id' not in st.session_state: - st.session_state.video_id = None -if 'video_segments' not in st.session_state: - st.session_state.video_segments = [] -if 'video_url' not in st.session_state: - st.session_state.video_url = None -if 'qa_results' not in st.session_state: - st.session_state.qa_results = [] -if 'qa_snippets' not in st.session_state: - st.session_state.qa_snippets = [] -if 'chapters_result' not in st.session_state: - st.session_state.chapters_result = None -if 'highlights_result' not in st.session_state: - st.session_state.highlights_result = None -if 'chapter_snippets' not in st.session_state: - st.session_state.chapter_snippets = [] -if 'highlight_snippets' not in st.session_state: - st.session_state.highlight_snippets = [] +initialize_session_state() def display_qa_snippet(file_name, query, snippet_info, snippet_index): """Display a QA video snippet with metadata.""" if os.path.exists(file_name): - st.write(f"### đŸŽ¯ Query: {query}") - st.write(f"⏰ **Timeframe:** {snippet_info['start_time_str']} - {snippet_info['end_time_str']} ({snippet_info['duration']:.1f}s)") - st.write(f"đŸŽ¯ **Confidence:** {snippet_info['confidence'] * 100:.1f}%") + st.write(f"### Query: {query}") + st.write(f"**Timeframe:** {snippet_info['start_time_str']} - {snippet_info['end_time_str']} ({snippet_info['duration']:.1f}s)") + st.write(f"**Confidence:** {snippet_info['confidence'] * 100:.1f}%") if snippet_info.get('text'): - st.write(f"đŸ’Ŧ **Content Preview:** {snippet_info['text'][:150]}...") + st.write(f"**Content Preview:** {snippet_info['text'][:150]}...") st.video(file_name) @@ -105,7 +558,7 @@ def process_qa_search(): """Process QA search and create video snippets.""" # Search scope selection - st.subheader("🔍 Search Scope") + st.subheader("Search Scope") col1, col2 = st.columns([1, 1]) with col1: @@ -121,7 +574,7 @@ def process_qa_search(): st.error("No video selected. Please upload or select a video first, or choose 'All videos in index'.") return elif search_scope == "All videos in index": - st.info("🌐 Searching across all videos in your TwelveLabs index") + st.info("Searching across all videos in your TwelveLabs index") # Check search readiness for current video (if applicable) if search_scope == "Current video only": @@ -130,14 +583,14 @@ def process_qa_search(): capabilities = get_video_qa_capabilities(client, st.session_state.video_id) if capabilities['ready_for_search']: - st.success("✅ Video is ready for Q&A search!") + st.success("Video is ready for Q&A search!") else: - st.warning("âŗ Video is still being processed for search. This can take a few minutes after upload.") + st.warning("Video is still being processed for search. This can take a few minutes after upload.") st.info(""" **What's happening?** - - Your video has been uploaded successfully ✅ - - Basic processing (timestamps) is complete ✅ - - Search indexing is still in progress âŗ + - Your video has been uploaded successfully + - Basic processing (timestamps) is complete + - Search indexing is still in progress **What to do:** - Wait 2-5 minutes and refresh this page @@ -150,12 +603,12 @@ def process_qa_search(): else: capabilities = {'ready_for_search': True} # Index search should always be available - query = st.text_input("🔍 Ask a question about the video(s):", + query = st.text_input("Ask a question about the video(s):", placeholder="e.g., 'What are the main topics discussed?', 'Show me the introduction', 'Find product demonstrations'", disabled=not capabilities['ready_for_search']) # Analysis options - st.subheader("📊 Analysis Options") + st.subheader("Analysis Options") col1, col2, col3 = st.columns([1, 1, 2]) with col1: @@ -173,11 +626,11 @@ def process_qa_search(): with col3: if analysis_mode == "Enhanced Analysis": - st.info("📝 Includes detailed analysis of each segment") + st.info("Includes detailed analysis of each segment") elif analysis_mode == "With Video Summary": - st.info("📖 Includes video summary + highlights + detailed segments") + st.info("Includes video summary + highlights + detailed segments") else: - st.info("⚡ Fast search with basic content preview") + st.info("Fast search with basic content preview") search_disabled = not capabilities['ready_for_search'] or not query @@ -223,7 +676,7 @@ def process_qa_search(): # Show additional analysis options if analysis_mode in ["Enhanced Analysis", "With Video Summary"]: - st.info("✨ Enhanced analysis powered by TwelveLabs multimodal understanding") + st.info("Enhanced analysis powered by TwelveLabs multimodal understanding") # Option to create video snippets # Note: Can only create snippets if we have video URLs @@ -233,7 +686,7 @@ def process_qa_search(): elif search_scope == "Current video only" and not st.session_state.video_url: st.info("Video snippets require streaming URL. Try refreshing the video URL first.") elif search_scope == "All videos in index": - st.info("💡 To create video snippets, search within a specific video that has streaming enabled.") + st.info("To create video snippets, search within a specific video that has streaming enabled.") except Exception as e: st.error(f"Error during search: {str(e)}") @@ -282,7 +735,7 @@ def create_qa_snippets(query, search_results): def display_video_analysis_section(): """Display standalone video analysis options for the current video.""" st.markdown("---") - st.subheader("📊 Video Analysis & Insights") + st.subheader("Video Analysis & Insights") st.write("Get comprehensive analysis of your current video") if not st.session_state.video_id: @@ -292,233 +745,87 @@ def display_video_analysis_section(): col1, col2, col3 = st.columns([1, 1, 1]) with col1: - if st.button("📝 Generate Summary", key="gen_summary_btn"): - try: - with st.spinner("Generating video summary..."): - client = TwelveLabs(api_key=API_KEY) - summary_result = generate_summary(client, st.session_state.video_id) - - st.subheader("📝 Video Summary") - st.write(summary_result['summary']) - - except Exception as e: - st.error(f"Error generating summary: {str(e)}") + if st.button("Generate Summary", key="gen_summary_btn"): + db_video_id = st.session_state.get('db_video_id') + if not db_video_id: + st.error("No database video ID found in session state.") + return + + summary_data = db.get_analysis(db_video_id, "summary") + if summary_data: + st.subheader("Video Summary") + st.write(summary_data['summary']) + else: + try: + with st.spinner("Generating video summary..."): + client = TwelveLabs(api_key=API_KEY) + summary_result = generate_summary(client, st.session_state.video_id) + db.save_analysis(db_video_id, "summary", summary_result) + st.subheader("Video Summary") + st.write(summary_result['summary']) + except Exception as e: + st.error(f"Error generating summary: {str(e)}") with col2: - if st.button("📑 Generate Chapters", key="gen_chapters_btn"): - try: - with st.spinner("Generating video chapters and creating snippets..."): - client = TwelveLabs(api_key=API_KEY) - chapters_result = generate_chapters(client, st.session_state.video_id) - - st.subheader("📑 Video Chapters with Snippets") - - # Store chapters result in session state - st.session_state.chapters_result = chapters_result - st.session_state.chapter_snippets = [] - - # Auto-create all snippets and display them - for chapter in chapters_result['chapters']: - start_time = seconds_to_mmss(chapter['start_sec']) - end_time = seconds_to_mmss(chapter['end_sec']) - duration = chapter['end_sec'] - chapter['start_sec'] - - st.markdown(f"### 📖 Chapter {chapter['chapter_number']}: {chapter['chapter_title']}") - st.write(f"**⏰ Time:** {start_time} - {end_time} ({duration:.1f}s)") - st.write(f"**📝 Summary:** {chapter['chapter_summary']}") - - # Try to create snippet automatically - snippet_created = False - snippet_filename = None - - if st.session_state.video_url and st.session_state.video_id: - try: - # Try using HLS-compatible method first - try: - snippet_filename = create_hls_snippet_alternative( - video_id=st.session_state.video_id, - start_time=chapter['start_sec'], - end_time=chapter['end_sec'], - title=chapter['chapter_title'], - snippet_type="chapter" - ) - snippet_created = True - except Exception as hls_error: - # Fallback to URL-based method - snippet_filename = create_analysis_video_snippet( - video_url=st.session_state.video_url, - start_time=chapter['start_sec'], - end_time=chapter['end_sec'], - title=chapter['chapter_title'], - snippet_type="chapter" - ) - snippet_created = True - - # Store snippet info - st.session_state.chapter_snippets.append({ - 'filename': snippet_filename, - 'title': chapter['chapter_title'], - 'chapter_number': chapter['chapter_number'] - }) - - except Exception as e: - st.warning(f"Could not create snippet: {str(e)}") - - # Display snippet or placeholder - col_video, col_download = st.columns([2, 1]) - - with col_video: - if snippet_created and os.path.exists(snippet_filename): - st.video(snippet_filename) - st.success("✅ Snippet ready!") - else: - # Show placeholder or HLS stream if available - if st.session_state.video_url: - st.info(f"📹 Video segment: {start_time} - {end_time}") - # Try to show the main video with timestamp info - try: - video_html = get_hls_player_html(st.session_state.video_url) - st.components.v1.html(video_html, height=300) - st.caption(f"⏰ Jump to {start_time} in the main video") - except: - st.warning("Video preview not available") - else: - st.info("đŸŽŦ Snippet creation requires video URL") - - with col_download: - if snippet_created and os.path.exists(snippet_filename): - with open(snippet_filename, "rb") as file: - file_contents = file.read() - st.download_button( - label=f"âŦ‡ī¸ Download", - data=file_contents, - file_name=snippet_filename, - mime="video/mp4", - key=f"download_chapter_{chapter['chapter_number']}", - help=f"Download Chapter {chapter['chapter_number']} snippet" - ) - else: - st.button( - "🔄 Retry Snippet", - key=f"retry_chapter_{chapter['chapter_number']}", - help="Try creating snippet again", - disabled=not st.session_state.video_url - ) - - st.markdown("---") - - except Exception as e: - st.error(f"Error generating chapters: {str(e)}") + if st.button("Generate Chapters", key="gen_chapters_btn"): + db_video_id = st.session_state.get('db_video_id') + if not db_video_id: + st.error("No database video ID found in session state.") + return + + chapters_data = db.get_analysis(db_video_id, "chapters") + if chapters_data: + st.subheader("Video Chapters") + for chapter in chapters_data['chapters']: + st.write(f"**{chapter['chapter_title']}**") + st.write(f"_{seconds_to_mmss(chapter['start_sec'])} - {seconds_to_mmss(chapter['end_sec'])}_") + st.write(chapter['chapter_summary']) + st.markdown("---") + else: + try: + with st.spinner("Generating video chapters..."): + client = TwelveLabs(api_key=API_KEY) + chapters_result = generate_chapters(client, st.session_state.video_id) + db.save_analysis(db_video_id, "chapters", chapters_result) + st.subheader("Video Chapters") + for chapter in chapters_result['chapters']: + st.write(f"**{chapter['chapter_title']}**") + st.write(f"_{seconds_to_mmss(chapter['start_sec'])} - {seconds_to_mmss(chapter['end_sec'])}_") + st.write(chapter['chapter_summary']) + st.markdown("---") + except Exception as e: + st.error(f"Error generating chapters: {str(e)}") with col3: - if st.button("✨ Generate Highlights", key="gen_highlights_btn"): - try: - with st.spinner("Generating video highlights and creating snippets..."): - client = TwelveLabs(api_key=API_KEY) - highlights_result = generate_highlights(client, st.session_state.video_id) - - st.subheader("✨ Video Highlights with Snippets") - - # Store highlights result in session state - st.session_state.highlights_result = highlights_result - st.session_state.highlight_snippets = [] - - # Auto-create all snippets and display them - for i, highlight in enumerate(highlights_result['highlights'], 1): - start_time = seconds_to_mmss(highlight['start_sec']) - end_time = seconds_to_mmss(highlight['end_sec']) - duration = highlight['end_sec'] - highlight['start_sec'] - - st.markdown(f"### ⭐ Highlight {i}: {highlight['highlight']}") - st.write(f"**⏰ Time:** {start_time} - {end_time} ({duration:.1f}s)") - if highlight.get('highlight_summary'): - st.write(f"**📝 Details:** {highlight['highlight_summary']}") - - # Try to create snippet automatically - snippet_created = False - snippet_filename = None - - if st.session_state.video_url and st.session_state.video_id: - try: - # Try using HLS-compatible method first - try: - snippet_filename = create_hls_snippet_alternative( - video_id=st.session_state.video_id, - start_time=highlight['start_sec'], - end_time=highlight['end_sec'], - title=highlight['highlight'], - snippet_type="highlight" - ) - snippet_created = True - except Exception as hls_error: - # Fallback to URL-based method - snippet_filename = create_analysis_video_snippet( - video_url=st.session_state.video_url, - start_time=highlight['start_sec'], - end_time=highlight['end_sec'], - title=highlight['highlight'], - snippet_type="highlight" - ) - snippet_created = True - - # Store snippet info - st.session_state.highlight_snippets.append({ - 'filename': snippet_filename, - 'title': highlight['highlight'], - 'highlight_number': i - }) - - except Exception as e: - st.warning(f"Could not create snippet: {str(e)}") - - # Display snippet or placeholder - col_video, col_download = st.columns([2, 1]) - - with col_video: - if snippet_created and os.path.exists(snippet_filename): - st.video(snippet_filename) - st.success("✅ Snippet ready!") - else: - # Show placeholder or HLS stream if available - if st.session_state.video_url: - st.info(f"📹 Video segment: {start_time} - {end_time}") - # Try to show the main video with timestamp info - try: - video_html = get_hls_player_html(st.session_state.video_url) - st.components.v1.html(video_html, height=300) - st.caption(f"⏰ Jump to {start_time} in the main video") - except: - st.warning("Video preview not available") - else: - st.info("đŸŽŦ Snippet creation requires video URL") - - with col_download: - if snippet_created and os.path.exists(snippet_filename): - with open(snippet_filename, "rb") as file: - file_contents = file.read() - st.download_button( - label=f"âŦ‡ī¸ Download", - data=file_contents, - file_name=snippet_filename, - mime="video/mp4", - key=f"download_highlight_{i}", - help=f"Download Highlight {i} snippet" - ) - else: - st.button( - "🔄 Retry Snippet", - key=f"retry_highlight_{i}", - help="Try creating snippet again", - disabled=not st.session_state.video_url - ) - - st.markdown("---") - - except Exception as e: - st.error(f"Error generating highlights: {str(e)}") + if st.button("Generate Highlights", key="gen_highlights_btn"): + db_video_id = st.session_state.get('db_video_id') + if not db_video_id: + st.error("No database video ID found in session state.") + return + + highlights_data = db.get_analysis(db_video_id, "highlights") + if highlights_data: + st.subheader("Video Highlights") + for highlight in highlights_data['highlights']: + st.write(f"**{highlight['highlight']}**") + st.write(f"_{seconds_to_mmss(highlight['start_sec'])} - {seconds_to_mmss(highlight['end_sec'])}_") + st.markdown("---") + else: + try: + with st.spinner("Generating video highlights..."): + client = TwelveLabs(api_key=API_KEY) + highlights_result = generate_highlights(client, st.session_state.video_id) + db.save_analysis(db_video_id, "highlights", highlights_result) + st.subheader("Video Highlights") + for highlight in highlights_result['highlights']: + st.write(f"**{highlight['highlight']}**") + st.write(f"_{seconds_to_mmss(highlight['start_sec'])} - {seconds_to_mmss(highlight['end_sec'])}_") + st.markdown("---") + except Exception as e: + st.error(f"Error generating highlights: {str(e)}") # Custom analysis section - st.subheader("đŸŽ¯ Custom Analysis") + st.subheader("Custom Analysis") custom_prompt = st.text_area( "Enter custom analysis prompt:", placeholder="e.g., 'Analyze the emotional tone of this video', 'List all products mentioned', 'Identify key learning objectives'", @@ -527,40 +834,52 @@ def display_video_analysis_section(): col1, col2 = st.columns([1, 1]) with col1: - if st.button("🔍 Analyze", key="custom_analysis_btn", disabled=not custom_prompt): - try: - with st.spinner("Performing custom analysis..."): - client = TwelveLabs(api_key=API_KEY) - analysis_result = generate_open_analysis( - client, - st.session_state.video_id, - custom_prompt, - temperature=0.3 - ) - - st.subheader("đŸŽ¯ Custom Analysis Results") - st.write(analysis_result['analysis']) - - except Exception as e: - st.error(f"Error performing custom analysis: {str(e)}") + if st.button("Analyze", key="custom_analysis_btn", disabled=not custom_prompt): + db_video_id = st.session_state.get('db_video_id') + if not db_video_id: + st.error("No database video ID found in session state.") + return + + analysis_type = f"custom_{custom_prompt[:50].replace(' ', '_')}" + custom_analysis = db.get_analysis(db_video_id, analysis_type) + + if custom_analysis: + st.subheader("Custom Analysis Results") + st.write(custom_analysis['analysis']) + else: + try: + with st.spinner("Performing custom analysis..."): + client = TwelveLabs(api_key=API_KEY) + analysis_result = generate_open_analysis( + client, + st.session_state.video_id, + custom_prompt, + temperature=0.3 + ) + db.save_analysis(db_video_id, analysis_type, analysis_result) + st.subheader("Custom Analysis Results") + st.write(analysis_result['analysis']) + except Exception as e: + st.error(f"Error performing custom analysis: {str(e)}") with col2: - st.info("💡 **Analysis Tips:**\n- Be specific in your prompts\n- Ask about content, themes, or patterns\n- Request summaries for specific audiences\n- Analyze emotional tone or sentiment") + st.info("**Analysis Tips:**\n- Be specific in your prompts\n- Ask about content, themes, or patterns\n- Request summaries for specific audiences\n- Analyze emotional tone or sentiment") + def display_qa_interface(): """Main QA interface display function.""" - st.subheader("🤖 Video Q&A Interface") + st.subheader("Video Q&A Interface") st.write("Ask questions about your videos and get relevant segments!") # Add helpful info about TwelveLabs search - with st.expander("â„šī¸ How TwelveLabs Video Search Works"): + with st.expander("How TwelveLabs Video Search Works"): st.markdown(""" **TwelveLabs Built-in Intelligence:** - 🧠 **No Manual Setup Required**: TwelveLabs automatically creates embeddings and vector indices when you upload videos + **No Manual Setup Required**: TwelveLabs automatically creates embeddings and vector indices when you upload videos - 🔍 **Multi-modal Search**: Searches across: + **Multi-modal Search**: Searches across: - **Visual content**: Objects, scenes, actions, people - **Audio content**: Speech, music, sounds, conversations - **Text content**: Any text visible in the video @@ -578,10 +897,10 @@ def display_qa_interface(): - "Locate discussions about pricing" **Multi-Video Benefits:** - - ✅ Find content across your entire video library - - ✅ Compare similar content between videos - - ✅ Discover patterns across different videos - - ✅ Access comprehensive search results + - Find content across your entire video library + - Compare similar content between videos + - Discover patterns across different videos + - Access comprehensive search results """) # QA Search Interface @@ -593,7 +912,7 @@ def display_qa_interface(): # Display created QA snippets if st.session_state.qa_snippets: - st.subheader("📹 Q&A Video Snippets") + st.subheader("Q&A Video Snippets") for index, (file_name, query, snippet_info) in enumerate(st.session_state.qa_snippets): display_qa_snippet(file_name, query, snippet_info, index) @@ -610,11 +929,11 @@ def display_qa_interface(): # Show helpful message when no video is selected but interface is accessible if not st.session_state.video_id: st.info(""" - 💡 **Pro Tip**: Even without a specific video selected, you can: + **Pro Tip**: Even without a specific video selected, you can: - Search across **all videos in your index** using the "All videos in index" option - Find content from any video in your TwelveLabs library - - Upload a new video in the **"📤 Upload Video"** tab - - Select an existing video in the **"📂 Select Existing"** tab + - Upload a new video in the **"Upload Video"** tab + - Select an existing video in the **"Select Existing"** tab """) @@ -664,34 +983,275 @@ def process_and_display_segments(): # Uplaoding feature and the processing of the video -def upload_and_process_video(): - video_type = st.selectbox("Select video type:", ["Basic Video (less than 30 mins)", "Podcast (30 mins to 1 hour)"]) - uploaded_file = st.file_uploader("Choose a video file", type=["mp4", "mov", "avi"]) +# Batch Processing Functions +def add_to_batch_queue(file_info): + """Add a video file to the batch processing queue.""" + batch_item = { + 'id': str(uuid.uuid4()), + 'filename': file_info['filename'], + 'size': file_info['size'], + 'type': file_info['type'], + 'status': 'queued', + 'video_id': None, + 'timestamps': None, + 'error': None, + 'progress': 0 + } + st.session_state.batch_queue.append(batch_item) + return batch_item['id'] - if uploaded_file and st.button("Process Video", key="process_video_button"): - with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file: - tmp_file.write(uploaded_file.read()) - video_path = tmp_file.name +def process_batch_queue(): + """Process all videos in the batch queue.""" + if not st.session_state.batch_queue: + return + + st.session_state.batch_processing = True + client = TwelveLabs(api_key=API_KEY) + + for i, item in enumerate(st.session_state.batch_queue): + if item['status'] != 'queued': + continue + try: - with st.spinner("Processing video..."): - client = TwelveLabs(api_key=API_KEY) - timestamps, video_id = process_video(client, video_path, video_type) - st.success("Video processed successfully!") - st.session_state.timestamps = timestamps - st.session_state.video_id = video_id - st.session_state.video_url = get_video_url(video_id) - if st.session_state.video_url: - st.video(st.session_state.video_url) - else: - st.info("Video processed successfully! Note: Video streaming is being prepared and may take a few moments to become available.") - except ValueError as e: - st.error(f"Configuration Error: {str(e)}") + item['status'] = 'processing' + item['progress'] = 25 + + # Here you would process the actual file + # For now, we'll simulate processing + timestamps, video_id = "00:00-Sample Chapter", f"video_{item['id'][:8]}" + + item['video_id'] = video_id + item['timestamps'] = timestamps + item['status'] = 'completed' + item['progress'] = 100 + + st.session_state.batch_results[item['id']] = { + 'video_id': video_id, + 'timestamps': timestamps, + 'filename': item['filename'] + } + except Exception as e: - st.error(f"Processing Error: {str(e)}") - if "api_key" in str(e).lower(): - st.info("This appears to be an API key issue. Please check your TwelveLabs API configuration.") - finally: - os.unlink(video_path) + item['status'] = 'error' + item['error'] = str(e) + display_enhanced_error('Batch Processing Error', str(e), + get_recovery_suggestions('processing_error')) + + st.session_state.batch_processing = False + +def display_batch_queue(): + """Display the current batch processing queue.""" + if not st.session_state.batch_queue: + return + + st.subheader("Batch Processing Queue") + + for item in st.session_state.batch_queue: + col1, col2, col3, col4 = st.columns([3, 2, 2, 1]) + + with col1: + st.write(f"{item['filename']}") + + with col2: + if item['status'] == 'queued': + st.write("Queued") + elif item['status'] == 'processing': + st.progress(item['progress'] / 100) + elif item['status'] == 'completed': + st.write("Completed") + elif item['status'] == 'error': + st.write("Error") + + with col3: + st.write(f"{item['size']} MB") + + with col4: + if st.button("Remove", key=f"remove_{item['id']}", help="Remove from queue"): + st.session_state.batch_queue.remove(item) + st.experimental_rerun() + +def upload_and_process_video(): + """Enhanced upload function with batch processing capabilities.""" + st.subheader("Video Upload") + + # Batch vs Single upload toggle + upload_mode = st.radio("Upload Mode:", ["Single Video", "Batch Upload"], horizontal=True) + + if upload_mode == "Single Video": + # Original single video upload + video_type = st.selectbox("Select video type:", ["Basic Video (less than 30 mins)", "Podcast (30 mins to 1 hour)"]) + uploaded_file = st.file_uploader("Choose a video file", type=["mp4", "mov", "avi"]) + + if uploaded_file and st.button("Process Video", key="process_video_button"): + # Store processing status + st.session_state.processing_status[uploaded_file.name] = { + 'status': 'processing', + 'progress': 0, + 'start_time': datetime.now() + } + + with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_file: + tmp_file.write(uploaded_file.read()) + video_path = tmp_file.name + try: + with st.spinner("Processing video..."): + # Update progress + st.session_state.processing_status[uploaded_file.name]['progress'] = 50 + + client = TwelveLabs(api_key=API_KEY) + timestamps, video_id = process_video(client, video_path, video_type) + + # Add to database + db.add_video(st.session_state.user_db_id, uploaded_file.name, video_id, 'ready') + + # Update progress + st.session_state.processing_status[uploaded_file.name]['progress'] = 100 + st.session_state.processing_status[uploaded_file.name]['status'] = 'completed' + + st.success("Video processed successfully!") + st.session_state.timestamps = timestamps + st.session_state.video_id = video_id + st.experimental_rerun() + except Exception as e: + display_enhanced_error('processing_error', str(e), get_recovery_suggestions('processing_error')) + # Update status in DB + if 'video_id' in locals() and video_id: + db.update_video_status(video_id, 'failed') + finally: + if 'video_path' in locals() and os.path.exists(video_path): + os.remove(video_path) + + elif upload_mode == "Batch Upload": + uploaded_files = st.file_uploader("Choose video files for batch processing", + type=["mp4", "mov", "avi"], accept_multiple_files=True) + + if uploaded_files: + for file in uploaded_files: + file_info = { + 'filename': file.name, + 'size': round(file.size / (1024 * 1024), 2), + 'type': file.type + } + add_to_batch_queue(file_info) + + display_batch_queue() + + if st.button("Start Batch Processing", key="start_batch_button"): + process_batch_queue() + st.success("Batch processing started. Check the queue for progress.") + st.experimental_rerun() + + st.session_state.video_id = video_id + st.session_state.video_url = get_video_url(video_id) + + # Store video metadata + st.session_state.video_metadata[video_id] = { + 'filename': uploaded_file.name, + 'upload_time': datetime.now(), + 'file_size': len(uploaded_file.getvalue()), + 'video_type': video_type + } + + if st.session_state.video_url: + st.video(st.session_state.video_url) + else: + st.info("Video processed successfully! Note: Video streaming is being prepared and may take a few moments to become available.") + + except Exception as e: + st.session_state.processing_status[uploaded_file.name]['status'] = 'error' + display_enhanced_error('Upload Error', str(e), get_recovery_suggestions('upload_error')) + finally: + os.unlink(video_path) + + else: + # Batch upload mode + st.write("**Batch Upload Mode** - Upload multiple videos for processing") + + uploaded_files = st.file_uploader( + "Choose video files", + type=["mp4", "mov", "avi"], + accept_multiple_files=True, + help="Select multiple video files to process in batch" + ) + + if uploaded_files: + st.write(f"Selected {len(uploaded_files)} files:") + + total_size = 0 + for file in uploaded_files: + file_size = len(file.getvalue()) / (1024 * 1024) # Convert to MB + total_size += file_size + st.write(f" â€ĸ {file.name} ({file_size:.1f} MB)") + + st.write(f"Total size: {total_size:.1f} MB") + + col1, col2, col3 = st.columns([1, 1, 1]) + + with col1: + if st.button("Add to Queue", key="add_to_queue"): + for file in uploaded_files: + file_info = { + 'filename': file.name, + 'size': len(file.getvalue()) / (1024 * 1024), + 'type': "Basic Video (less than 30 mins)" # Default type + } + add_to_batch_queue(file_info) + st.success(f"Added {len(uploaded_files)} files to processing queue!") + st.experimental_rerun() + + with col2: + if st.button("Process Queue", key="process_queue", + disabled=st.session_state.batch_processing or not st.session_state.batch_queue): + process_batch_queue() + st.success("Batch processing completed!") + st.experimental_rerun() + + with col3: + if st.button("Clear Queue", key="clear_queue"): + st.session_state.batch_queue = [] + st.session_state.batch_results = {} + st.success("Queue cleared!") + st.experimental_rerun() + + # Display batch queue + display_batch_queue() + + # Display batch results + if st.session_state.batch_results: + st.subheader("Batch Results") + for batch_id, result in st.session_state.batch_results.items(): + with st.expander(f"{result['filename']}"): + st.write(f"**Video ID:** {result['video_id']}") + st.write("**Timestamps:**") + st.code(result['timestamps']) + + # Quick export for batch results + if st.button(f"Copy Timestamps", key=f"copy_batch_{batch_id}"): + st.components.v1.html(f""" + + """, height=0) + st.success("Timestamps copied to clipboard!") + + # Display processing status for persistence + if st.session_state.processing_status: + st.subheader("Processing Status") + for filename, status in st.session_state.processing_status.items(): + col1, col2, col3 = st.columns([2, 1, 1]) + with col1: + st.write(f"{filename}") + with col2: + if status['status'] == 'processing': + st.progress(status['progress'] / 100) + elif status['status'] == 'completed': + st.write("Completed") + elif status['status'] == 'error': + st.write("Error") + with col3: + if 'start_time' in status: + elapsed = datetime.now() - status['start_time'] + st.write(f"Time: {elapsed.seconds}s") # Selecting the existing video from the Index and generating timestamps highlight def select_existing_video(): @@ -735,26 +1295,57 @@ def select_existing_video(): def display_timestamps_and_segments(): if st.session_state.timestamps: st.subheader("YouTube Chapter Timestamps") - st.write("Copy the Timestamp description and add it to the Youtube Video Description") + + # Enhanced export section + with st.expander("Export Options", expanded=True): + st.write("Choose your export format:") + + tab1, tab2, tab3 = st.tabs(["YouTube", "JSON", "CSV"]) + + with tab1: + create_export_button({ + 'qa_results': st.session_state.qa_results, + 'chapters': st.session_state.chapters_result, + 'highlights': st.session_state.highlights_result + }, export_type="youtube") + + with tab2: + create_export_button({ + 'qa_results': st.session_state.qa_results, + 'chapters': st.session_state.chapters_result, + 'highlights': st.session_state.highlights_result + }, export_type="json") + + with tab3: + create_export_button({ + 'qa_results': st.session_state.qa_results, + 'chapters': st.session_state.chapters_result, + 'highlights': st.session_state.highlights_result + }, export_type="csv") + + # Original display + st.write("**Raw Timestamps:**") st.code(st.session_state.timestamps, language="") # Check if video URL is available or try to refresh it if not st.session_state.video_url and st.session_state.video_id: if st.button("Refresh Video URL", key="refresh_video_url_button"): - st.session_state.video_url = get_video_url(st.session_state.video_id) - if st.session_state.video_url: - st.success("Video URL is now available!") - st.experimental_rerun() - else: - st.info("Video streaming is still being prepared. Please try again in a few moments.") + try: + st.session_state.video_url = get_video_url(st.session_state.video_id) + if st.session_state.video_url: + st.success("Video URL is now available!") + st.experimental_rerun() + else: + st.info("Video streaming is still being prepared. Please try again in a few moments.") + except Exception as e: + display_enhanced_error('URL Refresh Error', str(e), get_recovery_suggestions('api_error')) if st.session_state.video_url: if st.button("Create Video Segments", key="create_segments_button"): try: process_and_display_segments() except Exception as e: - st.error(f"Error creating video segments: {str(e)}") - st.exception(e) # This will display the full traceback + display_enhanced_error('Segment Creation Error', str(e), get_recovery_suggestions('processing_error')) else: st.info("Video segments cannot be created because the video streaming URL is not yet available. This may take a few moments after upload. Try refreshing the video URL above.") @@ -771,14 +1362,14 @@ def display_timestamps_and_segments(): st.success("All segment files have been cleared.") st.experimental_rerun() -def main(): +def run_app(): # Configuration status check try: # Test if we can create a TwelveLabs client client = TwelveLabs(api_key=API_KEY) - st.success("✅ TwelveLabs API configuration is valid!") + st.success("TwelveLabs API configuration is valid!") except Exception as e: - st.error(f"❌ TwelveLabs API configuration error: {str(e)}") + st.error(f"TwelveLabs API configuration error: {str(e)}") st.info(""" **Setup Instructions:** 1. Copy `.env.example` to `.env` @@ -790,7 +1381,7 @@ def main(): return # Main navigation tabs - tab1, tab2, tab3 = st.tabs(["**📤 Upload Video**", "**📂 Select Existing**", "**🤖 Video Q&A**"]) + tab1, tab2, tab3 = st.tabs(["Upload Video", "Select Existing", "Video Q&A"]) with tab1: upload_and_process_video() @@ -804,5 +1395,31 @@ def main(): # Display timestamps and segments (shown on all tabs when available) display_timestamps_and_segments() +def main(): + st.set_page_config( + page_title="đŸŽŦ HootQnA - AI Video Analysis Platform", + page_icon="đŸŽŦ", + layout="wide", + initial_sidebar_state="expanded" + ) + + # Auth0 login + auth_info = sac.login_button( + clientId=AUTH0_CLIENT_ID, + domain=AUTH0_DOMAIN, + ) + + if not auth_info: + st.warning("Please log in to access the application.") + st.info("This is a demo application. You can use a dummy email and password to log in if you don't have an account.") + st.stop() + + st.sidebar.success(f"Welcome, {auth_info['name']}!") + + with st.sidebar: + sac.logout_button() + + run_app() + if __name__ == "__main__": main() \ No newline at end of file diff --git a/auth_config.py b/auth_config.py new file mode 100644 index 0000000..c16776d --- /dev/null +++ b/auth_config.py @@ -0,0 +1,8 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN") +AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID") +AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET") diff --git a/database.py b/database.py new file mode 100644 index 0000000..e6eade0 --- /dev/null +++ b/database.py @@ -0,0 +1,150 @@ +import sqlite3 +import json +import logging + +DATABASE_NAME = "video_analysis.db" + +def get_db_connection(): + """Establishes a connection to the database.""" + conn = sqlite3.connect(DATABASE_NAME, check_same_thread=False) + conn.row_factory = sqlite3.Row + return conn + +def init_db(): + """Initializes the database and creates tables if they don't exist.""" + conn = get_db_connection() + cursor = conn.cursor() + + # User table to store user info from Auth0 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + auth0_user_id TEXT UNIQUE NOT NULL, + email TEXT, + name TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Videos table to store video metadata + cursor.execute(''' + CREATE TABLE IF NOT EXISTS videos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + twelvelabs_video_id TEXT UNIQUE, + filename TEXT, + status TEXT NOT NULL, -- e.g., 'processing', 'ready', 'failed' + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) + ) + ''') + + # Analyses table to store results like summaries, chapters, etc. + cursor.execute(''' + CREATE TABLE IF NOT EXISTS analyses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + video_id INTEGER NOT NULL, + analysis_type TEXT NOT NULL, -- e.g., 'summary', 'chapters', 'highlights' + result TEXT NOT NULL, -- Storing the result as a JSON string + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (video_id) REFERENCES videos (id), + UNIQUE(video_id, analysis_type) + ) + ''') + + conn.commit() + conn.close() + logging.info("Database initialized successfully.") + +def get_or_create_user(user_info): + """ + Finds a user by their Auth0 user_id or creates a new one. + Returns the internal user ID. + """ + auth0_user_id = user_info.get('sub') + email = user_info.get('email') + name = user_info.get('name') + + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute("SELECT id FROM users WHERE auth0_user_id = ?", (auth0_user_id,)) + user = cursor.fetchone() + + if user: + user_id = user['id'] + else: + cursor.execute( + "INSERT INTO users (auth0_user_id, email, name) VALUES (?, ?, ?)", + (auth0_user_id, email, name) + ) + conn.commit() + user_id = cursor.lastrowid + + conn.close() + return user_id + +def add_video(user_id, filename, twelvelabs_video_id, status='processing'): + """Adds a new video record to the database.""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute( + "INSERT INTO videos (user_id, filename, twelvelabs_video_id, status) VALUES (?, ?, ?, ?)", + (user_id, filename, twelvelabs_video_id, status) + ) + conn.commit() + video_id = cursor.lastrowid + conn.close() + return video_id + +def update_video_status(twelvelabs_video_id, status): + """Updates the processing status of a video.""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute( + "UPDATE videos SET status = ? WHERE twelvelabs_video_id = ?", + (status, twelvelabs_video_id) + ) + conn.commit() + conn.close() + +def get_user_videos(user_id): + """Retrieves all videos for a given user.""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM videos WHERE user_id = ? ORDER BY created_at DESC", (user_id,)) + videos = cursor.fetchall() + conn.close() + return videos + +def get_video_by_id(video_id): + """Retrieves a single video by its internal ID.""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT * FROM videos WHERE id = ?", (video_id,)) + video = cursor.fetchone() + conn.close() + return video + +def save_analysis(video_id, analysis_type, result_data): + """Saves the result of an analysis to the database.""" + conn = get_db_connection() + cursor = conn.cursor() + result_json = json.dumps(result_data) + cursor.execute( + "INSERT OR REPLACE INTO analyses (video_id, analysis_type, result) VALUES (?, ?, ?)", + (video_id, analysis_type, result_json) + ) + conn.commit() + conn.close() + +def get_analysis(video_id, analysis_type): + """Retrieves a previously saved analysis result.""" + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT result FROM analyses WHERE video_id = ? AND analysis_type = ?", (video_id, analysis_type)) + analysis = cursor.fetchone() + conn.close() + if analysis: + return json.loads(analysis['result']) + return None diff --git a/requirements.txt b/requirements.txt index f006934..e289255 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ requests python-dotenv m3u8 yt_dlp +streamlit-auth0-component +python-jose diff --git a/style.css b/style.css new file mode 100644 index 0000000..b07a9ad --- /dev/null +++ b/style.css @@ -0,0 +1,310 @@ +/* Dark Blue Theme */ +[data-testid="stAppViewContainer"] { + background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%); + min-height: 100vh; + color: white; +} + +[data-testid="stHeader"] { + background-color: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(10px); +} + +[data-testid="stToolbar"] { + right: 2rem; + background: rgba(15, 23, 42, 0.8); + border-radius: 8px; +} + +[data-testid="stSidebar"] { + background-color: #1e293b; +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; + font-weight: 600 !important; + color: #f8fafc !important; + letter-spacing: -0.02em !important; +} + +h1 { + background: linear-gradient(135deg, #60a5fa, #3b82f6); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +p, div, span, label { + color: #e2e8f0 !important; +} + +/* Buttons */ +.stButton > button { + background: linear-gradient(145deg, #3b82f6, #1d4ed8); + color: white !important; + border: 1px solid #2563eb; + border-radius: 12px; + font-weight: 600; + padding: 0.75rem 1.5rem; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stButton > button:hover { + background: linear-gradient(145deg, #1d4ed8, #1e40af); + box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4); + transform: translateY(-2px); + border-color: #3b82f6; +} + +.stDownloadButton > button { + background: linear-gradient(145deg, #059669, #047857) !important; + color: white !important; + border: 1px solid #10b981; + border-radius: 12px; + font-weight: 600; + padding: 0.75rem 1.5rem; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(5, 150, 105, 0.3); +} + +.stDownloadButton > button:hover { + background: linear-gradient(145deg, #047857, #065f46) !important; + box-shadow: 0 6px 20px rgba(5, 150, 105, 0.4); + transform: translateY(-2px); +} + +/* Alert Messages */ +.stSuccess { + background: linear-gradient(135deg, #064e3b, #047857); + border: 1px solid #10b981; + border-radius: 12px; + color: #6ee7b7 !important; + box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2); +} + +.stError { + background: linear-gradient(135deg, #7f1d1d, #991b1b); + border: 1px solid #ef4444; + border-radius: 12px; + color: #fca5a5 !important; + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2); +} + +.stInfo { + background: linear-gradient(135deg, #1e3a8a, #1d4ed8); + border: 1px solid #3b82f6; + border-radius: 12px; + color: #93c5fd !important; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2); +} + +.stWarning { + background: linear-gradient(135deg, #92400e, #b45309); + border: 1px solid #f59e0b; + border-radius: 12px; + color: #fbbf24 !important; + box-shadow: 0 4px 12px rgba(245, 158, 11, 0.2); +} + +/* Tabs */ +.stTabs [data-baseweb="tab-list"] { + background-color: #1e293b; + border-radius: 12px; + padding: 0.5rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.stTabs [data-baseweb="tab-list"] button { + background-color: transparent; + border-radius: 8px; + color: #94a3b8 !important; + font-weight: 500; + padding: 0.75rem 1.5rem; + margin: 0 0.25rem; + transition: all 0.3s ease; +} + +.stTabs [data-baseweb="tab-list"] button[aria-selected="true"] { + background: linear-gradient(145deg, #3b82f6, #1d4ed8); + color: white !important; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); + font-weight: 600; +} + +.stTabs [data-baseweb="tab-list"] button:hover { + background-color: #334155; + color: #e2e8f0 !important; +} + +/* Expandable sections */ +.streamlit-expanderHeader { + background: linear-gradient(135deg, #1e293b, #334155); + border-radius: 12px; + border: 1px solid #475569; + color: #f8fafc !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + +/* Input fields */ +.stTextInput > div > div > input { + border-radius: 12px; + border: 1px solid #475569; + background-color: #1e293b !important; + color: #f8fafc !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + padding: 0.75rem 1rem; +} + +.stTextInput > div > div > input:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +.stTextArea > div > div > textarea { + border-radius: 12px; + border: 1px solid #475569; + background-color: #1e293b !important; + color: #f8fafc !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + padding: 0.75rem 1rem; +} + +.stTextArea > div > div > textarea:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +.stSelectbox > div > div > select { + border-radius: 12px; + border: 1px solid #475569; + background-color: #1e293b !important; + color: #f8fafc !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + padding: 0.75rem 1rem; +} + +/* Progress bar */ +.stProgress > div > div > div > div { + background: linear-gradient(90deg, #3b82f6, #1d4ed8); + border-radius: 4px; + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); +} + +/* Video container */ +[data-testid="stVideo"] { + border-radius: 16px; + overflow: hidden; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + border: 1px solid #475569; +} + +/* Code blocks */ +.stCodeBlock { + border-radius: 12px; + border: 1px solid #475569; + background-color: #0f172a !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +/* Main container */ +.main .block-container { + padding-top: 2rem; + padding-bottom: 2rem; + max-width: 1200px; +} + +/* Card containers */ +.stContainer { + background: linear-gradient(135deg, #1e293b, #334155); + border-radius: 16px; + padding: 2rem; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + margin-bottom: 1.5rem; + border: 1px solid #475569; +} + +/* Notifications */ +.notification { + position: fixed; + top: 20px; + right: 20px; + background: linear-gradient(145deg, #059669, #047857); + color: white; + padding: 16px 24px; + border-radius: 12px; + z-index: 1000; + animation: slideIn 0.3s ease-in-out; + box-shadow: 0 8px 24px rgba(5, 150, 105, 0.3); + font-weight: 600; + border: 1px solid #10b981; +} + +@keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +/* Keyboard shortcuts */ +.shortcut-hint { + font-size: 0.7em; + color: #94a3b8; + margin-left: 8px; + background-color: #0f172a; + padding: 4px 8px; + border-radius: 6px; + font-family: 'Courier New', monospace; + border: 1px solid #475569; + font-weight: normal; +} + +/* File uploader */ +[data-testid="stFileUploader"] { + background: linear-gradient(135deg, #1e293b, #334155); + border: 2px dashed #475569; + border-radius: 16px; + padding: 2rem; + text-align: center; + transition: all 0.3s ease; +} + +[data-testid="stFileUploader"]:hover { + border-color: #3b82f6; + background: linear-gradient(135deg, #334155, #475569); +} + +/* Sidebar styling */ +.css-1d391kg { + background-color: #1e293b; +} + +/* Metrics */ +[data-testid="metric-container"] { + background: linear-gradient(135deg, #1e293b, #334155); + border: 1px solid #475569; + padding: 1rem; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +/* Custom icon classes */ +.icon { + margin-right: 8px; + color: #3b82f6; +} + +.icon-upload::before { content: "\f093"; } +.icon-video::before { content: "\f03d"; } +.icon-search::before { content: "\f002"; } +.icon-download::before { content: "\f019"; } +.icon-play::before { content: "\f04b"; } +.icon-question::before { content: "\f059"; } +.icon-export::before { content: "\f56e"; } +.icon-batch::before { content: "\f0cb"; } +.icon-error::before { content: "\f071"; } +.icon-success::before { content: "\f00c"; } +.icon-info::before { content: "\f05a"; } +.icon-keyboard::before { content: "\f11c"; }