Hug0endob commited on
Commit
0f16c98
·
verified ·
1 Parent(s): d6942ee

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +98 -85
streamlit_app.py CHANGED
@@ -2,7 +2,7 @@
2
  # -*- coding: utf-8 -*-
3
 
4
  """
5
- Video‑analysis Streamlit app (refactored, sidebar‑based controls, no experimental_rerun).
6
  """
7
 
8
  # ----------------------------------------------------------------------
@@ -119,8 +119,8 @@ def _download_with_yt_dlp(url: str, dst: Path, password: str = "") -> Path:
119
  "nocheckcertificate": True,
120
  "merge_output_format": "mp4",
121
  "fragment_retries": 0,
122
- "force_ipv4": True, # <‑‑ force IPv4
123
- "retries": 3, # retry a few times on transient errors
124
  "socket_timeout": 30,
125
  }
126
  if password:
@@ -220,7 +220,7 @@ def _expand_sidebar(width: int = 380) -> None:
220
  st.markdown(
221
  f"""
222
  <style>
223
- .css-1d391kg {{ /* class name may change with Streamlit updates */
224
  width: {width}px !important;
225
  min-width: {width}px !important;
226
  }}
@@ -230,30 +230,71 @@ def _expand_sidebar(width: int = 380) -> None:
230
  )
231
 
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  # ----------------------------------------------------------------------
234
  # Streamlit UI
235
  # ----------------------------------------------------------------------
236
  def main() -> None:
237
  st.set_page_config(page_title="Video Analysis", layout="wide")
 
238
  _expand_sidebar()
239
 
240
  # ---------- Sidebar ----------
241
  st.sidebar.header("Video Input")
242
- st.sidebar.text_input("Video URL", key="url", placeholder="https://")
243
-
244
- # Clear cached videos
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  if st.sidebar.button("Clear Video"):
246
  for f in DATA_DIR.iterdir():
247
  try:
248
  f.unlink()
249
  except Exception:
250
  pass
251
- st.session_state["video_path"] = ""
252
- st.session_state["analysis_out"] = ""
253
- st.session_state["raw_output"] = ""
 
 
 
 
 
 
254
  st.toast("All cached videos cleared")
255
 
256
- # Load video button
257
  if st.sidebar.button("Load Video"):
258
  try:
259
  with st.spinner("Downloading video…"):
@@ -261,12 +302,15 @@ def main() -> None:
261
  st.session_state["url"], DATA_DIR, st.session_state["video_password"]
262
  )
263
  mp4_path = _convert_to_mp4(Path(raw_path))
264
- # Optional compression based on user‑defined limit
265
  mp4_path, _ = _maybe_compress(mp4_path, st.session_state["compress_mb"])
266
  st.session_state["video_path"] = str(mp4_path)
267
  st.session_state["last_error"] = ""
268
  st.toast("Video ready")
269
- except Exception as e:
 
 
 
 
270
  st.session_state["last_error"] = f"Download failed: {e}"
271
  st.sidebar.error(st.session_state["last_error"])
272
 
@@ -276,13 +320,12 @@ def main() -> None:
276
  "Model", MODEL_OPTIONS, index=MODEL_OPTIONS.index(DEFAULT_MODEL)
277
  )
278
  if model == "custom":
279
- model = st.text_input("Custom model ID", value=DEFAULT_MODEL, key="custom_model")
 
 
280
  st.session_state["model_input"] = model
281
 
282
- # API key handling – can be set via env var or entered here
283
- secret_key = os.getenv("GOOGLE_API_KEY", "")
284
- if secret_key:
285
- st.session_state["api_key"] = secret_key
286
  st.text_input(
287
  "Google API Key",
288
  key="api_key",
@@ -296,45 +339,41 @@ def main() -> None:
296
  key="prompt",
297
  height=140,
298
  )
299
- st.text_input(
300
- "Video password (if needed)",
301
- key="video_password",
302
- type="password",
303
- )
304
- st.number_input(
305
- "Compress if > (MB)",
306
- min_value=10,
307
- max_value=2000,
308
- value=st.session_state.get("compress_mb", 200),
309
- step=10,
310
- key="compress_mb",
311
- )
312
 
313
  # ---------- Main panel ----------
314
- # Run Analysis button
315
- if st.button("Run Analysis"):
316
- if not st.session_state.get("video_path"):
317
- st.sidebar.error("No video loaded – load a video first.")
318
- else:
319
- st.session_state["busy"] = True
320
- st.session_state["analysis_out"] = ""
321
- st.session_state["raw_output"] = ""
322
- try:
323
- with st.spinner("Generating report…"):
324
- raw = generate_report(
325
- Path(st.session_state["video_path"]),
326
- st.session_state["prompt"],
327
- st.session_state["model_input"],
328
- )
329
- cleaned = _strip_prompt_echo(st.session_state["prompt"], raw)
330
- st.session_state["analysis_out"] = cleaned
331
- st.session_state["raw_output"] = raw
332
- except Exception as e:
333
- st.session_state["last_error"] = f"Analysis failed: {e}"
334
- st.session_state["last_error_detail"] = traceback.format_exc()
335
- finally:
336
- st.session_state["busy"] = False
337
-
 
 
 
 
 
 
 
 
 
338
  if st.session_state.get("analysis_out"):
339
  st.subheader("📝 Analysis")
340
  st.write(st.session_state["analysis_out"])
@@ -344,44 +383,18 @@ def main() -> None:
344
 
345
  if st.session_state.get("video_path"):
346
  st.video(st.session_state["video_path"])
347
-
348
- if st.session_state["last_error"]:
349
  st.error(st.session_state["last_error"])
350
-
351
  if st.session_state.get("last_error_detail"):
352
  with st.expander("Show error details"):
353
  st.code(st.session_state["last_error_detail"], language="text")
354
 
355
- # ----------------------------------------------------------------------
356
- # Session‑state defaults
357
- # ----------------------------------------------------------------------
358
- def _init_state() -> None:
359
- defaults = {
360
- "url": "",
361
- "video_path": "",
362
- "model_input": DEFAULT_MODEL,
363
- "prompt": DEFAULT_PROMPT,
364
- "api_key": os.getenv("GOOGLE_API_KEY", "AIzaSyBiAW2GQLid0HGe9Vs_ReKwkwsSVNegNzs"),
365
- "video_password": "",
366
- "compress_mb": 200,
367
- "busy": False,
368
- "last_error": "",
369
- "analysis_out": "",
370
- "raw_output": "",
371
- "last_error_detail": "",
372
- }
373
- for k, v in defaults.items():
374
- st.session_state.setdefault(k, v)
375
-
376
 
377
  # ----------------------------------------------------------------------
378
  # Entry point
379
  # ----------------------------------------------------------------------
380
  if __name__ == "__main__":
381
- _init_state()
382
-
383
- # Configure Gemini if an API key is present (env var or sidebar entry)
384
- if st.session_state["api_key"]:
385
- genai.configure(api_key=st.session_state["api_key"])
386
-
387
  main()
 
2
  # -*- coding: utf-8 -*-
3
 
4
  """
5
+ Video‑analysis Streamlit app
6
  """
7
 
8
  # ----------------------------------------------------------------------
 
119
  "nocheckcertificate": True,
120
  "merge_output_format": "mp4",
121
  "fragment_retries": 0,
122
+ "force_ipv4": True,
123
+ "retries": 3,
124
  "socket_timeout": 30,
125
  }
126
  if password:
 
220
  st.markdown(
221
  f"""
222
  <style>
223
+ section[data-testid="stSidebar"] {{
224
  width: {width}px !important;
225
  min-width: {width}px !important;
226
  }}
 
230
  )
231
 
232
 
233
+ # ----------------------------------------------------------------------
234
+ # Session‑state defaults
235
+ # ----------------------------------------------------------------------
236
+ def _init_state() -> None:
237
+ defaults = {
238
+ "url": "",
239
+ "video_path": "",
240
+ "model_input": DEFAULT_MODEL,
241
+ "prompt": DEFAULT_PROMPT,
242
+ "api_key": os.getenv("GOOGLE_API_KEY", "AIzaSyBiAW2GQLid0HGe9Vs_ReKwkwsSVNegNzs"),
243
+ "video_password": "",
244
+ "compress_mb": 200,
245
+ "busy": False,
246
+ "last_error": "",
247
+ "analysis_out": "",
248
+ "raw_output": "",
249
+ "last_error_detail": "",
250
+ }
251
+ for k, v in defaults.items():
252
+ st.session_state.setdefault(k, v)
253
+
254
+
255
  # ----------------------------------------------------------------------
256
  # Streamlit UI
257
  # ----------------------------------------------------------------------
258
  def main() -> None:
259
  st.set_page_config(page_title="Video Analysis", layout="wide")
260
+ _init_state() # initialise after config
261
  _expand_sidebar()
262
 
263
  # ---------- Sidebar ----------
264
  st.sidebar.header("Video Input")
265
+ st.sidebar.text_input(
266
+ "Video URL", key="url", placeholder="https://"
267
+ )
268
+ st.sidebar.text_input(
269
+ "Video password (if needed)",
270
+ key="video_password",
271
+ type="password",
272
+ )
273
+ st.sidebar.number_input(
274
+ "Compress if > (MB)",
275
+ min_value=10,
276
+ max_value=2000,
277
+ value=st.session_state.get("compress_mb", 200),
278
+ step=10,
279
+ key="compress_mb",
280
+ )
281
  if st.sidebar.button("Clear Video"):
282
  for f in DATA_DIR.iterdir():
283
  try:
284
  f.unlink()
285
  except Exception:
286
  pass
287
+ st.session_state.update(
288
+ {
289
+ "video_path": "",
290
+ "analysis_out": "",
291
+ "raw_output": "",
292
+ "last_error": "",
293
+ "last_error_detail": "",
294
+ }
295
+ )
296
  st.toast("All cached videos cleared")
297
 
 
298
  if st.sidebar.button("Load Video"):
299
  try:
300
  with st.spinner("Downloading video…"):
 
302
  st.session_state["url"], DATA_DIR, st.session_state["video_password"]
303
  )
304
  mp4_path = _convert_to_mp4(Path(raw_path))
 
305
  mp4_path, _ = _maybe_compress(mp4_path, st.session_state["compress_mb"])
306
  st.session_state["video_path"] = str(mp4_path)
307
  st.session_state["last_error"] = ""
308
  st.toast("Video ready")
309
+ except (
310
+ RuntimeError,
311
+ requests.exceptions.RequestException,
312
+ yt_dlp.utils.DownloadError,
313
+ ) as e:
314
  st.session_state["last_error"] = f"Download failed: {e}"
315
  st.sidebar.error(st.session_state["last_error"])
316
 
 
320
  "Model", MODEL_OPTIONS, index=MODEL_OPTIONS.index(DEFAULT_MODEL)
321
  )
322
  if model == "custom":
323
+ model = st.text_input(
324
+ "Custom model ID", value=DEFAULT_MODEL, key="custom_model"
325
+ )
326
  st.session_state["model_input"] = model
327
 
328
+ # API key – can also be set via env var
 
 
 
329
  st.text_input(
330
  "Google API Key",
331
  key="api_key",
 
339
  key="prompt",
340
  height=140,
341
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
  # ---------- Main panel ----------
344
+ # Run Analysis button (placed after settings for visual flow)
345
+ if st.button("Run Analysis"):
346
+ if not st.session_state.get("video_path"):
347
+ st.error("No video loaded – load a video first.")
348
+ elif not st.session_state.get("api_key"):
349
+ st.error("Google API key missing – enter it in the sidebar.")
350
+ else:
351
+ # configure Gemini now that we have a key
352
+ genai.configure(api_key=st.session_state["api_key"])
353
+
354
+ st.session_state["busy"] = True
355
+ st.session_state["analysis_out"] = ""
356
+ st.session_state["raw_output"] = ""
357
+ st.session_state["last_error"] = ""
358
+ st.session_state["last_error_detail"] = ""
359
+
360
+ try:
361
+ with st.spinner("Generating report (this may take a minute)…"):
362
+ raw = generate_report(
363
+ Path(st.session_state["video_path"]),
364
+ st.session_state["prompt"],
365
+ st.session_state["model_input"],
366
+ )
367
+ cleaned = _strip_prompt_echo(st.session_state["prompt"], raw)
368
+ st.session_state["analysis_out"] = cleaned
369
+ st.session_state["raw_output"] = raw
370
+ except Exception as e:
371
+ st.session_state["last_error"] = f"Analysis failed: {e}"
372
+ st.session_state["last_error_detail"] = traceback.format_exc()
373
+ finally:
374
+ st.session_state["busy"] = False
375
+
376
+ # ---- Layout: analysis first, then video, then errors ----
377
  if st.session_state.get("analysis_out"):
378
  st.subheader("📝 Analysis")
379
  st.write(st.session_state["analysis_out"])
 
383
 
384
  if st.session_state.get("video_path"):
385
  st.video(st.session_state["video_path"])
386
+
387
+ if st.session_state.get("last_error"):
388
  st.error(st.session_state["last_error"])
389
+
390
  if st.session_state.get("last_error_detail"):
391
  with st.expander("Show error details"):
392
  st.code(st.session_state["last_error_detail"], language="text")
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
  # ----------------------------------------------------------------------
396
  # Entry point
397
  # ----------------------------------------------------------------------
398
  if __name__ == "__main__":
399
+ # No need to call _init_state() here – it is invoked inside main()
 
 
 
 
 
400
  main()