Dhruv-Ty commited on
Commit
5cb7f5d
·
verified ·
1 Parent(s): 75e1e14

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +338 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,340 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from phi.agent import Agent
3
+ from phi.model.google import Gemini
4
+ from phi.tools.duckduckgo import DuckDuckGo
5
+ from google.generativeai import upload_file, get_file
6
+ import google.generativeai as genai
7
+ import time
8
+ from pathlib import Path
9
+ import tempfile
10
+ from dotenv import load_dotenv
11
+ import os
12
+ import yt_dlp
13
+ import re
14
 
15
+ load_dotenv()
16
+
17
+ API_KEY = os.getenv("GOOGLE_API_KEY")
18
+ if API_KEY:
19
+ genai.configure(api_key=API_KEY)
20
+
21
+ # Page configuration
22
+ st.set_page_config(
23
+ page_title="The Plug",
24
+ page_icon="📹"
25
+ )
26
+
27
+ st.title("The Plug")
28
+
29
+ @st.cache_resource
30
+ def initialize_agent():
31
+ return Agent(
32
+ name="Video AI Summarizer",
33
+ model=Gemini(id="gemini-2.0-flash-exp"),
34
+ tools=[DuckDuckGo()],
35
+ markdown=True,
36
+ )
37
+
38
+ def download_video(url):
39
+ """Download video from URL using yt-dlp"""
40
+ temp_video = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
41
+ temp_video.close() # Close the file handle so yt-dlp can write to it
42
+
43
+ ydl_opts = {
44
+ 'outtmpl': temp_video.name,
45
+ 'format': 'best[ext=mp4]/best',
46
+ 'quiet': False, # Changed to False to see download progress
47
+ 'no_warnings': False, # Changed to False to see warnings
48
+ 'nooverwrites': False, # Ensure we don't skip downloads due to cache
49
+ # Anti-bot measures
50
+ 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
51
+ 'referer': 'https://www.youtube.com/',
52
+ 'http_headers': {
53
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
54
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
55
+ 'Accept-Language': 'en-us,en;q=0.5',
56
+ 'Sec-Fetch-Mode': 'navigate',
57
+ },
58
+ # Retry and timeout settings
59
+ 'retries': 3,
60
+ 'fragment_retries': 3,
61
+ 'retry_sleep_functions': {
62
+ 'http': lambda n: min(60, 2 ** n),
63
+ 'fragment': lambda n: min(60, 2 ** n),
64
+ },
65
+ 'extractor_retries': 3,
66
+ 'socket_timeout': 30,
67
+ # Additional options to avoid detection
68
+ 'nocheckcertificate': True,
69
+ 'ignoreerrors': False,
70
+ 'logtostderr': False,
71
+ 'sleep_interval': 1,
72
+ 'max_sleep_interval': 5,
73
+ }
74
+
75
+ # Try different download strategies
76
+ download_strategies = [
77
+ ydl_opts, # Default configuration
78
+ {**ydl_opts, 'format': '18/22/37/38', 'extract_flat': False}, # Specific format codes
79
+ {**ydl_opts, 'cookiesfrombrowser': None, 'ignoreerrors': True}, # Try without cookies
80
+ ]
81
+
82
+ last_error = None
83
+ for attempt, strategy in enumerate(download_strategies):
84
+ try:
85
+ st.info(f"Download attempt {attempt + 1}/3...")
86
+ with yt_dlp.YoutubeDL(strategy) as ydl:
87
+ ydl.download([url])
88
+
89
+ # Verify the file was downloaded and has content
90
+ if os.path.exists(temp_video.name) and os.path.getsize(temp_video.name) > 0:
91
+ st.success(f"Download successful on attempt {attempt + 1}")
92
+ return temp_video.name
93
+ else:
94
+ raise Exception("Downloaded file is empty or doesn't exist")
95
+
96
+ except Exception as e:
97
+ last_error = e
98
+ st.warning(f"Attempt {attempt + 1} failed: {str(e)}")
99
+
100
+ # Clean up failed download
101
+ if os.path.exists(temp_video.name):
102
+ try:
103
+ os.unlink(temp_video.name)
104
+ except:
105
+ pass
106
+
107
+ # Don't retry on the last attempt
108
+ if attempt < len(download_strategies) - 1:
109
+ # Wait before retrying with increasing delay
110
+ wait_time = min(30, 5 * (attempt + 1))
111
+ st.info(f"Waiting {wait_time} seconds before retry...")
112
+ time.sleep(wait_time)
113
+ continue
114
+
115
+ # All attempts failed
116
+ raise Exception(f"All download attempts failed. Last error: {str(last_error)}")
117
+
118
+ def is_valid_url(url):
119
+ """Check if URL is from supported platforms"""
120
+ patterns = [
121
+ r'(https?://)?(www\.)?(youtube\.com|youtu\.be)',
122
+ r'(https?://)?(www\.)?(instagram\.com|instagr\.am)',
123
+ r'(https?://)?(www\.)?(tiktok\.com)',
124
+ r'(https?://)?(www\.)?(twitter\.com|x\.com)',
125
+ ]
126
+
127
+ for pattern in patterns:
128
+ if re.search(pattern, url, re.IGNORECASE):
129
+ return True
130
+ return False
131
+
132
+ # Initialize the agent
133
+ multimodal_Agent = initialize_agent()
134
+
135
+ # Initialize session state for video caching and page navigation
136
+ if 'video_path' not in st.session_state:
137
+ st.session_state.video_path = None
138
+ if 'current_video_url' not in st.session_state:
139
+ st.session_state.current_video_url = None
140
+ if 'current_video_file' not in st.session_state:
141
+ st.session_state.current_video_file = None
142
+ if 'last_input_method' not in st.session_state:
143
+ st.session_state.last_input_method = None
144
+ if 'current_page' not in st.session_state:
145
+ st.session_state.current_page = "upload" # "upload" or "chat"
146
+ if 'chat_history' not in st.session_state:
147
+ st.session_state.chat_history = []
148
+
149
+ def cleanup_video_cache():
150
+ """Clean up cached video file and reset session"""
151
+ if st.session_state.video_path and os.path.exists(st.session_state.video_path):
152
+ Path(st.session_state.video_path).unlink(missing_ok=True)
153
+ st.session_state.video_path = None
154
+ st.session_state.current_video_url = None
155
+ st.session_state.current_video_file = None
156
+ st.session_state.current_page = "upload"
157
+ st.session_state.chat_history = []
158
+
159
+ # Page routing based on current state
160
+ if st.session_state.current_page == "upload":
161
+ # UPLOAD PAGE
162
+ st.subheader("Upload Your Video")
163
+
164
+ # Input method selection
165
+ input_method = st.radio(
166
+ "Choose how to provide your video:",
167
+ ["Upload Video File", "Paste Video Link"],
168
+ help="Select how you want to provide the video for analysis"
169
+ )
170
+
171
+ # Clean up cache if user switches input methods
172
+ if st.session_state.last_input_method != input_method:
173
+ cleanup_video_cache()
174
+ st.session_state.last_input_method = input_method
175
+
176
+ video_path = None
177
+
178
+ if input_method == "Upload Video File":
179
+ # File uploader
180
+ video_file = st.file_uploader(
181
+ "Upload a video file",
182
+ type=['mp4', 'mov', 'avi'],
183
+ help="Upload a video for AI analysis"
184
+ )
185
+
186
+ if video_file:
187
+ # Check if this is a different file than the cached one
188
+ if (st.session_state.current_video_file != video_file.name or
189
+ st.session_state.video_path is None or
190
+ not os.path.exists(st.session_state.video_path)):
191
+
192
+ # Clean up previous video if it exists
193
+ if st.session_state.video_path and os.path.exists(st.session_state.video_path):
194
+ Path(st.session_state.video_path).unlink(missing_ok=True)
195
+
196
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_video:
197
+ temp_video.write(video_file.read())
198
+ st.session_state.video_path = temp_video.name
199
+ st.session_state.current_video_file = video_file.name
200
+ st.session_state.current_video_url = None # Reset URL cache
201
+
202
+ video_path = st.session_state.video_path
203
+
204
+ else: # Paste Video Link
205
+ # URL input
206
+ video_url = st.text_input(
207
+ "Paste video link",
208
+ placeholder="https://youtube.com/watch?v=... or Instagram/TikTok/X link",
209
+ help="Paste a video URL from YouTube, Instagram, TikTok, or X"
210
+ )
211
+
212
+ if video_url:
213
+ if is_valid_url(video_url):
214
+ # Check if this is a different URL than the cached one
215
+ if (st.session_state.current_video_url != video_url or
216
+ st.session_state.video_path is None or
217
+ not os.path.exists(st.session_state.video_path)):
218
+
219
+ # Clean up previous video if it exists
220
+ if st.session_state.video_path and os.path.exists(st.session_state.video_path):
221
+ Path(st.session_state.video_path).unlink(missing_ok=True)
222
+
223
+ try:
224
+ with st.spinner("Downloading video..."):
225
+ st.session_state.video_path = download_video(video_url)
226
+ st.session_state.current_video_url = video_url
227
+ st.session_state.current_video_file = None # Reset file cache
228
+ except Exception as e:
229
+ st.error(f"Error downloading video: {e}")
230
+ st.session_state.video_path = None
231
+ st.session_state.current_video_url = None
232
+
233
+ video_path = st.session_state.video_path
234
+ else:
235
+ st.warning("Please enter a valid YouTube, Instagram, TikTok, or X video URL")
236
+
237
+ # Proceed to chat if video is loaded
238
+ if video_path and os.path.exists(video_path) and os.path.getsize(video_path) > 0:
239
+ if st.button("Start Chat", type="primary", use_container_width=True):
240
+ st.session_state.current_page = "chat"
241
+ st.rerun()
242
+
243
+ elif st.session_state.current_page == "chat":
244
+ # CHAT PAGE
245
+ video_path = st.session_state.video_path
246
+
247
+ if not video_path or not os.path.exists(video_path) or os.path.getsize(video_path) == 0:
248
+ st.error("Video not found. Please upload a video first.")
249
+ if st.button("Back to Upload"):
250
+ st.session_state.current_page = "upload"
251
+ st.rerun()
252
+ else:
253
+ # Navigation and controls
254
+ col1, col2 = st.columns([3, 1])
255
+ with col1:
256
+ st.subheader("Chat with Your Video")
257
+ with col2:
258
+ if st.button("New Video", help="Upload a different video"):
259
+ cleanup_video_cache()
260
+ st.session_state.current_page = "upload"
261
+ st.rerun()
262
+
263
+ # Display chat history
264
+ if st.session_state.chat_history:
265
+ st.markdown("### Chat History")
266
+ for i, (query, response) in enumerate(st.session_state.chat_history):
267
+ with st.container():
268
+ st.markdown(f"**You:** {query}")
269
+ st.markdown(f"**AI:** {response}")
270
+ st.divider()
271
+
272
+ # Chat input
273
+ st.markdown("### Ask a Question")
274
+ user_query = st.text_input(
275
+ "What would you like to know about this video?",
276
+ placeholder="Example: What is the main topic? Summarize the key points...",
277
+ help="Ask any question about the video content.",
278
+ key="chat_input"
279
+ )
280
+
281
+ if st.button("Send", type="primary", use_container_width=True):
282
+ if not user_query.strip():
283
+ st.warning("Please enter a question about the video.")
284
+ else:
285
+ try:
286
+ with st.spinner("Analyzing video and gathering insights..."):
287
+ # Upload and process video file
288
+ processed_video = upload_file(video_path)
289
+
290
+ while processed_video.state.name == "PROCESSING":
291
+ time.sleep(1)
292
+ processed_video = get_file(processed_video.name)
293
+
294
+ # Prompt generation for analysis
295
+ analysis_prompt = (
296
+ f"""
297
+ You are an expert video analyst. Analyze the uploaded video and respond to this query:
298
+
299
+ Query: {user_query}
300
+
301
+ Provide a comprehensive, insightful response that includes:
302
+ 1. Direct analysis of the video content
303
+ 2. Key insights and observations
304
+ 3. Any supplementary context that would be helpful
305
+ 4. Actionable takeaways
306
+
307
+ Be conversational and engaging while being thorough and accurate.
308
+ """
309
+ )
310
+
311
+ # AI agent processing
312
+ response = multimodal_Agent.run(analysis_prompt, videos=[processed_video])
313
+
314
+ # Add to chat history
315
+ st.session_state.chat_history.append((user_query, response.content))
316
+
317
+ # Rerun to update chat history display
318
+ st.rerun()
319
+
320
+ except Exception as error:
321
+ st.error(f"An error occurred during analysis: {error}")
322
+
323
+ # Customize UI styling
324
+ st.markdown(
325
+ """
326
+ <style>
327
+ .stTextInput input {
328
+ min-height: 40px;
329
+ }
330
+ .stRadio [role="radiogroup"] {
331
+ display: flex;
332
+ gap: 20px;
333
+ }
334
+ .stRadio label {
335
+ font-weight: 500;
336
+ }
337
+ </style>
338
+ """,
339
+ unsafe_allow_html=True
340
+ )