userIdc2024 commited on
Commit
c531990
·
verified ·
1 Parent(s): 4963e6f

Update src/streamlit_app.py

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