userIdc2024 commited on
Commit
4b65643
·
verified ·
1 Parent(s): 137cf47

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +519 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,521 @@
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 google import genai
3
+ import tempfile
4
+ import os
5
+ import time
6
+ import json
7
+ from typing import Optional
8
+ import pandas as pd
9
+ import logging
10
+ from database import insert_analysis_result
11
+ from dotenv import load_dotenv
12
+
13
+ load_dotenv()
14
+ # Backend API Key Configuration
15
+ GEMINI_API_KEY = os.getenv("GEMINI_KEY")
16
+
17
+ # Page configuration
18
+ st.set_page_config(
19
+ page_title="Video Analyser and Script Generator",
20
+ page_icon="🎥",
21
+ layout="wide",
22
+ initial_sidebar_state="expanded"
23
+ )
24
+
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format="%(asctime)s [%(levelname)s] %(message)s",
28
+ handlers=[
29
+ logging.StreamHandler()
30
+ ]
31
+ )
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def configure_gemini():
36
+ """Configure Gemini API with backend key"""
37
+ return genai.Client(api_key=GEMINI_API_KEY)
38
+
39
+ # Enhanced system prompt with timestamp-based improvements
40
+ SYSTEM_PROMPT = f"""{os.getenv("SYS_PROMPT")}"""
41
+
42
+ def analyze_video_and_generate_script(
43
+ video_bytes,
44
+ video_name,
45
+ offer_details: str = "",
46
+ target_audience: str = "",
47
+ specific_hooks: str = "",
48
+ additional_context: str = ""
49
+ ):
50
+ """
51
+ Analyze video and generate direct response script variations
52
+ """
53
+ try:
54
+ # Save uploaded video to temporary file
55
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(video_name)[1]) as tmp_file:
56
+ tmp_file.write(video_bytes)
57
+ tmp_file_path = tmp_file.name
58
+
59
+ # Configure Gemini
60
+ client = configure_gemini()
61
+
62
+ # Show upload progress
63
+ upload_progress = st.progress(0)
64
+ upload_status = st.empty()
65
+
66
+ upload_status.text("Uploading video to Google AI...")
67
+ upload_progress.progress(20)
68
+
69
+ # Upload video to Gemini
70
+ video_file_obj = client.files.upload(file=tmp_file_path)
71
+ upload_progress.progress(40)
72
+
73
+ upload_status.text("Processing video...")
74
+ while video_file_obj.state.name == "PROCESSING":
75
+ time.sleep(2)
76
+ video_file_obj = client.files.get(name=video_file_obj.name)
77
+ upload_progress.progress(60)
78
+
79
+ if video_file_obj.state.name == "FAILED":
80
+ upload_status.error("Google AI file processing failed. Please try another video.")
81
+ return None
82
+
83
+ upload_progress.progress(80)
84
+ upload_status.text("Generating script variations...")
85
+
86
+ # Build the enhanced user prompt
87
+ user_prompt = f"""Analyze this reference video and generate 3 high-converting direct response video script variations with detailed timestamp-based improvements.
88
+
89
+ IMPORTANT CONTEXT TO FOLLOW WHEN CREATING OUTPUT:
90
+ - Offer Details: {offer_details}
91
+ - Target Audience: {target_audience}
92
+ - Specific Hooks: {specific_hooks}
93
+
94
+ ADDITIONAL CONTEXT (MANDATORY TO FOLLOW):
95
+ {additional_context}
96
+
97
+ You must reflect this additional context in:
98
+ - The script tone, CTA, visuals
99
+ - Compliance or branding constraints
100
+ - Any assumptions about audience or product
101
+
102
+ Failure to include this will be considered incomplete.
103
+
104
+ Please provide a comprehensive analysis including:
105
+
106
+ 1. DETAILED VIDEO ANALYSIS with timestamp-based metrics:
107
+ - Break down the video into 5-10 second segments
108
+ - Rate each segment's effectiveness (1-10 scale)
109
+ - Identify specific elements (hook, transition, proof, CTA, etc.)
110
+
111
+ 2. TIMESTAMP-BASED IMPROVEMENTS:
112
+ - Specific recommendations for each time segment
113
+ - Priority level for each improvement
114
+ - Expected impact of implementing changes
115
+
116
+ 3. SCRIPT VARIATIONS:
117
+ - Create 2-3 complete script variations
118
+ - Each with timestamp-by-timestamp breakdown
119
+ - Different psychological triggers and approaches
120
+
121
+ IMPORTANT: Return only valid JSON in the exact format specified in the system prompt. Analyze the video second-by-second for maximum detail."""
122
+
123
+ # Generate response
124
+ response = client.models.generate_content(
125
+ model="gemini-2.0-flash",
126
+ contents=[video_file_obj, user_prompt + "\n\n" + SYSTEM_PROMPT]
127
+ )
128
+
129
+ upload_progress.progress(100)
130
+ upload_status.success("Analysis complete!")
131
+
132
+ # Clean up temporary file
133
+ os.unlink(tmp_file_path)
134
+
135
+ # Parse JSON response
136
+ try:
137
+ response_text = response.text.strip()
138
+ if response_text.startswith('```json'):
139
+ response_text = response_text[7:-3]
140
+ elif response_text.startswith('```'):
141
+ response_text = response_text[3:-3]
142
+
143
+ json_response = json.loads(response_text)
144
+ return json_response
145
+
146
+ except json.JSONDecodeError as e:
147
+ st.error(f"Error parsing AI response: {str(e)}")
148
+ return None
149
+
150
+ except Exception as e:
151
+ st.error(f"Error processing video: {str(e)}")
152
+ return None
153
+
154
+ def display_script_variations(json_data):
155
+ """Display script variations in formatted tables"""
156
+ if not json_data or "script_variations" not in json_data:
157
+ st.error("No script variations found in the response")
158
+ return
159
+
160
+ for i, variation in enumerate(json_data["script_variations"], 1):
161
+ variation_name = variation.get("variation_name", f"Variation {i}")
162
+
163
+ st.markdown(f"### Variation {i}: {variation_name}")
164
+
165
+ #Convert script table to DataFrame for better display
166
+ script_data = variation.get("script_table")
167
+ if not script_data:
168
+ st.warning(f"No script data for {variation_name}")
169
+ continue
170
+
171
+ df = pd.DataFrame(script_data)
172
+
173
+ # Rename columns for better display
174
+ df = df.rename(columns={
175
+ 'timestamp': 'Timestamp',
176
+ 'script_voiceover': 'Script / Voiceover',
177
+ 'visual_direction': 'Visual Direction',
178
+ 'psychological_trigger': 'Psychological Trigger',
179
+ 'cta_action': 'CTA / Action'
180
+ })
181
+
182
+ st.table(df)
183
+ st.markdown("---")
184
+
185
+
186
+ def display_video_analysis(json_data):
187
+ """Display video analysis in tabular format"""
188
+ if not json_data or "video_analysis" not in json_data:
189
+ st.error("No video analysis found in the response")
190
+ return
191
+
192
+ analysis = json_data["video_analysis"]
193
+
194
+ #Display general analysis
195
+ video_metrics = []
196
+ if isinstance(analysis, dict):
197
+ col1, col2 = st.columns(2)
198
+
199
+ with col1:
200
+ st.subheader("Effectiveness Factors")
201
+ st.write(analysis.get('effectiveness_factors', 'N/A'))
202
+
203
+ st.subheader("Target Audience")
204
+ st.write(analysis.get('target_audience', 'N/A'))
205
+
206
+ with col2:
207
+ st.subheader("Psychological Triggers")
208
+ st.write(analysis.get('psychological_triggers', 'N/A'))
209
+
210
+ video_metrics = analysis.get("video_metrics", [])
211
+
212
+ else:
213
+ st.warning("Unexpected format in video_analysis. Skipping metadata.")
214
+ if isinstance(analysis, list):
215
+ video_metrics = analysis
216
+
217
+ if video_metrics:
218
+ metrics_df = pd.DataFrame(video_metrics)
219
+
220
+ # Rename columns for better display
221
+ column_mapping = {
222
+ 'timestamp': 'Timestamp',
223
+ 'element': 'Element',
224
+ 'current_approach': 'Current Approach',
225
+ 'effectiveness_score': 'Score',
226
+ 'notes': 'Analysis Notes'
227
+ }
228
+
229
+ metrics_df = metrics_df.rename(columns=column_mapping)
230
+
231
+ st.dataframe(
232
+ metrics_df,
233
+ use_container_width=True,
234
+ hide_index=True,
235
+ column_config={
236
+ "Timestamp": st.column_config.TextColumn(width="small"),
237
+ "Element": st.column_config.TextColumn(width="medium"),
238
+ "Current Approach": st.column_config.TextColumn(width="large"),
239
+ "Score": st.column_config.TextColumn(width="small"),
240
+ "Analysis Notes": st.column_config.TextColumn(width="large")
241
+ }
242
+ )
243
+ else:
244
+ st.warning("No detailed video metrics available")
245
+
246
+ def display_timestamp_improvements(json_data):
247
+ """Display timestamp-based improvements in tabular format"""
248
+ improvements = json_data.get("timestamp_improvements")
249
+
250
+ if improvements is None:
251
+ st.error("No timestamp improvements found in the response")
252
+ return
253
+
254
+ if not improvements:
255
+ st.warning("No timestamp improvements available")
256
+ return
257
+
258
+ st.subheader("Timestamp-by-Timestamp Improvement Recommendations")
259
+
260
+ improvements = json_data["timestamp_improvements"]
261
+ if improvements:
262
+ improvements_df = pd.DataFrame(improvements)
263
+
264
+ # Rename columns for better display
265
+ column_mapping = {
266
+ 'timestamp': 'Timestamp',
267
+ 'current_element': 'Current Element',
268
+ 'improvement_type': 'Improvement Type',
269
+ 'recommended_change': 'Recommended Change',
270
+ 'expected_impact': 'Expected Impact',
271
+ 'priority': 'Priority'
272
+ }
273
+
274
+ improvements_df = improvements_df.rename(columns=column_mapping)
275
+
276
+ # Color code priority
277
+ def color_priority(val):
278
+ if val == 'High':
279
+ return 'background-color: #ffcccb'
280
+ elif val == 'Medium':
281
+ return 'background-color: #ffffcc'
282
+ elif val == 'Low':
283
+ return 'background-color: #ccffcc'
284
+ return ''
285
+
286
+ styled_df = improvements_df.style.applymap(color_priority, subset=['Priority'])
287
+
288
+ st.dataframe(
289
+ styled_df,
290
+ use_container_width=True,
291
+ hide_index=True,
292
+ column_config={
293
+ "Timestamp": st.column_config.TextColumn(width="small"),
294
+ "Current Element": st.column_config.TextColumn(width="medium"),
295
+ "Improvement Type": st.column_config.TextColumn(width="medium"),
296
+ "Recommended Change": st.column_config.TextColumn(width="large"),
297
+ "Expected Impact": st.column_config.TextColumn(width="medium"),
298
+ "Priority": st.column_config.TextColumn(width="small")
299
+ }
300
+ )
301
+ else:
302
+ st.warning("No timestamp improvements available")
303
+
304
+ def create_csv_download(json_data):
305
+ """Create CSV content with all scripts combined"""
306
+ all_scripts_data = []
307
+
308
+ # Combine all script variations into one dataset
309
+ for i, variation in enumerate(json_data.get("script_variations", []), 1):
310
+ variation_name = variation.get("variation_name", f"Variation {i}")
311
+
312
+ for row in variation.get("script_table", []):
313
+ script_row = {
314
+ 'Variation': variation_name,
315
+ 'Timestamp': row.get('timestamp', ''),
316
+ 'Script_Voiceover': row.get('script_voiceover', ''),
317
+ 'Visual_Direction': row.get('visual_direction', ''),
318
+ 'Psychological_Trigger': row.get('psychological_trigger', ''),
319
+ 'CTA_Action': row.get('cta_action', '')
320
+ }
321
+ all_scripts_data.append(script_row)
322
+
323
+ # Convert to DataFrame and then to CSV
324
+ if all_scripts_data:
325
+ df = pd.DataFrame(all_scripts_data)
326
+ return df.to_csv(index=False)
327
+ else:
328
+ return "No script data available"
329
+
330
+ def check_token(user_token):
331
+ ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
332
+ if not ACCESS_TOKEN:
333
+ logger.critical("ACCESS_TOKEN not set in environment.")
334
+ return False, "Server error: Access token not configured."
335
+ if user_token == ACCESS_TOKEN:
336
+ logger.info("Access token validated successfully.")
337
+ return True, ""
338
+ logger.warning("Invalid access token attempt.")
339
+ return False, "Invalid token."
340
+
341
+ def main():
342
+ """Main application function"""
343
+
344
+ st.set_page_config(
345
+ page_title="Video Analyser and Script Generator",
346
+ page_icon="🎥",
347
+ layout="wide",
348
+ initial_sidebar_state="expanded"
349
+ )
350
+
351
+ st.title("Video Analyser and Script Generator")
352
+ st.divider()
353
+
354
+ if "authenticated" not in st.session_state:
355
+ st.session_state["authenticated"] = False
356
+
357
+ if not st.session_state["authenticated"]:
358
+ st.markdown("## Access Required")
359
+ token_input = st.text_input("Enter Access Token", type="password")
360
+ if st.button("Unlock App"):
361
+ ok, error_msg = check_token(token_input)
362
+ if ok:
363
+ st.session_state["authenticated"] = True
364
+ st.rerun()
365
+ else:
366
+ st.error(error_msg)
367
+ return
368
+
369
+
370
+ # Sidebar navigation
371
+ if st.session_state["authenticated"]:
372
+
373
+ selected_tab = st.sidebar.radio("Select Mode", ["Script Generator", "History"])
374
+
375
+ # ========== SCRIPT GENERATOR ==========
376
+ if selected_tab == "Script Generator":
377
+ with st.expander("How to Use This Tool", expanded=False):
378
+ st.markdown("""
379
+ ### Upload Guidelines:
380
+ - **Best videos to analyze**: Already profitable Facebook/TikTok ads in your niche
381
+ - **Video length**: 30–90 seconds work best for analysis
382
+ - **Quality**: Clear audio and visuals help with better analysis
383
+
384
+ ### Context Tips:
385
+ - **Offer details**: Be specific about your main promise and mechanism
386
+ - **Audience**: Include demographics, pain points, and desires
387
+ - **Hooks**: Mention any specific angles that have worked for you
388
+
389
+ ### Script Optimization:
390
+ - Generated scripts focus on stopping scroll and driving clicks
391
+ - Each variation tests different psychological triggers
392
+ - Use the timestamp format for precise video production
393
+ - Test multiple variations to find your best performer
394
+ """)
395
+ st.subheader("Input Configuration")
396
+
397
+ uploaded_video = st.file_uploader(
398
+ "Upload Reference Video",
399
+ type=['mp4', 'mov', 'avi', 'mkv'],
400
+ help="Upload a profitable ad video to analyze and create variations from"
401
+ )
402
+ if uploaded_video is None:
403
+ st.info("Please upload a reference video to begin analysis.")
404
+
405
+ st.subheader("Additional Context (Optional)")
406
+
407
+ offer_details = st.text_area(
408
+ "Offer Details",
409
+ placeholder="e.g., Solar installation with $0 down payment...",
410
+ height=80,
411
+ help="Describe the product/service and main promise"
412
+ )
413
+
414
+ target_audience = st.text_area(
415
+ "Target Audience",
416
+ placeholder="e.g., 40+ homeowners with high electricity bills...",
417
+ height=80,
418
+ help="Describe the ideal customer demographics and pain points"
419
+ )
420
+
421
+ specific_hooks = st.text_area(
422
+ "Specific Hooks to Test",
423
+ placeholder="e.g., Government rebate angle, celebrity endorsement...",
424
+ height=80,
425
+ help="Any specific angles or hooks you want to incorporate"
426
+ )
427
+
428
+ additional_context = st.text_area(
429
+ "Additional Context",
430
+ placeholder="Any other relevant information...",
431
+ height=100,
432
+ help="Compliance requirements, brand guidelines, or other notes"
433
+ )
434
+
435
+ generate_button = st.button("Generate Script Variations", use_container_width=True)
436
+
437
+ if "analysis_results" in st.session_state and st.session_state["analysis_results"]:
438
+ if st.button("Clear Results", use_container_width=True):
439
+ del st.session_state["analysis_results"]
440
+ st.rerun()
441
+
442
+ # Generate & show results
443
+ if uploaded_video and generate_button:
444
+ with st.spinner("Analyzing video and generating scripts..."):
445
+ video_bytes = uploaded_video.read()
446
+ uploaded_video.seek(0)
447
+
448
+ json_response = analyze_video_and_generate_script(
449
+ video_bytes,
450
+ uploaded_video.name,
451
+ offer_details,
452
+ target_audience,
453
+ specific_hooks,
454
+ additional_context
455
+ )
456
+
457
+ if json_response:
458
+ insert_analysis_result(
459
+ video_name=uploaded_video.name,
460
+ offer_details=offer_details,
461
+ target_audience=target_audience,
462
+ specific_hook=specific_hooks,
463
+ additional_context=additional_context,
464
+ response=json_response
465
+ )
466
+ st.session_state["analysis_results"] = json_response
467
+
468
+ if "analysis_results" in st.session_state:
469
+ json_response = st.session_state["analysis_results"]
470
+
471
+ tab1, tab2, tab3 = st.tabs(["Script Variations", "Video Analysis", "Improvement Recommendations"])
472
+ with tab1:
473
+ display_script_variations(json_response)
474
+ csv_content = create_csv_download(json_response)
475
+ st.download_button("Download All Scripts (CSV)", data=csv_content,
476
+ file_name="video_script_variations.csv", mime="text/csv")
477
+ with tab2:
478
+ display_video_analysis(json_response)
479
+ with tab3:
480
+ display_timestamp_improvements(json_response)
481
+
482
+ # ========== HISTORY ==========
483
+ elif selected_tab == "History":
484
+ from database import get_all_results
485
+ history_items = get_all_results(limit=20)
486
+
487
+ if history_items:
488
+ video_titles = [
489
+ f"{item['video_name']} ({item['created_at'].strftime('%Y-%m-%d %H:%M')})"
490
+ for item in history_items
491
+ ]
492
+
493
+ selected = st.sidebar.radio("History Items", video_titles, index=0)
494
+ selected_index = video_titles.index(selected)
495
+ selected_data = history_items[selected_index]
496
+
497
+ st.subheader(f"Analysis for: {selected_data['video_name']}")
498
+ json_response = selected_data.get("response")
499
+
500
+ if json_response:
501
+ tab1, tab2, tab3 = st.tabs(["Script Variations", "Video Analysis", "Improvement Recommendations"])
502
+
503
+ with tab1:
504
+ display_script_variations(json_response)
505
+ with tab2:
506
+ display_video_analysis(json_response)
507
+ with tab3:
508
+ display_timestamp_improvements(json_response)
509
+ else:
510
+ st.warning("No valid response data for this analysis.")
511
+ else:
512
+ st.sidebar.info("No saved analyses found.")
513
+ st.info("No saved history available.")
514
+
515
 
516
+ if __name__ == "__main__":
517
+ try:
518
+ logger.info("Launching Streamlit app...")
519
+ main()
520
+ except Exception as e:
521
+ logger.exception("Unhandled error during app launch.")