Hug0endob commited on
Commit
3559725
·
verified ·
1 Parent(s): 1cd718a

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +119 -22
streamlit_app.py CHANGED
@@ -44,13 +44,11 @@ MODEL_OPTIONS = [
44
  # Helper utilities
45
  # ----------------------------------------------------------------------
46
  def _sanitize_filename(url: str) -> str:
47
- """Make a safe filename from a URL."""
48
  name = Path(url).name.lower()
49
  return name.translate(str.maketrans("", "", string.punctuation)).replace(" ", "_")
50
 
51
 
52
  def _file_sha256(path: Path) -> Optional[str]:
53
- """Return SHA‑256 hex digest of *path* or None on error."""
54
  try:
55
  h = hashlib.sha256()
56
  with path.open("rb") as f:
@@ -62,7 +60,6 @@ def _file_sha256(path: Path) -> Optional[str]:
62
 
63
 
64
  def _convert_to_mp4(src: Path) -> Path:
65
- """Convert *src* to MP4 (ffmpeg) and delete the original."""
66
  dst = src.with_suffix(".mp4")
67
  if dst.exists():
68
  return dst
@@ -79,7 +76,6 @@ def _convert_to_mp4(src: Path) -> Path:
79
 
80
 
81
  def _compress_video(inp: Path, crf: int = 28, preset: str = "fast") -> Path:
82
- """Compress *inp* with libx264; return the new file."""
83
  out = inp.with_name(f"{inp.stem}_compressed.mp4")
84
  try:
85
  ffmpeg.input(str(inp)).output(
@@ -91,7 +87,6 @@ def _compress_video(inp: Path, crf: int = 28, preset: str = "fast") -> Path:
91
 
92
 
93
  def _maybe_compress(path: Path, limit_mb: int) -> Tuple[Path, bool]:
94
- """Compress *path* if it exceeds *limit_mb*."""
95
  size_mb = path.stat().st_size / (1024 * 1024)
96
  if size_mb <= limit_mb:
97
  return path, False
@@ -99,7 +94,6 @@ def _maybe_compress(path: Path, limit_mb: int) -> Tuple[Path, bool]:
99
 
100
 
101
  def _download_direct(url: str, dst: Path) -> Path:
102
- """Simple HTTP GET download."""
103
  r = requests.get(url, stream=True, timeout=30)
104
  r.raise_for_status()
105
  out = dst / _sanitize_filename(url.split("/")[-1])
@@ -111,7 +105,6 @@ def _download_direct(url: str, dst: Path) -> Path:
111
 
112
 
113
  def _download_with_yt_dlp(url: str, dst: Path, password: str = "") -> Path:
114
- """Download via yt‑dlp, returning the newest MP4."""
115
  tmpl = str(dst / "%(id)s.%(ext)s")
116
  fmt = "best[ext=mp4]/best"
117
 
@@ -156,7 +149,6 @@ def _download_with_yt_dlp(url: str, dst: Path, password: str = "") -> Path:
156
  raise RuntimeError("No MP4 file was created.")
157
  newest = max(mp4_files, key=lambda p: p.stat().st_mtime)
158
 
159
- # Deduplicate via SHA‑256 cache
160
  sha = _file_sha256(newest)
161
  if sha:
162
  for existing in dst.iterdir():
@@ -167,7 +159,6 @@ def _download_with_yt_dlp(url: str, dst: Path, password: str = "") -> Path:
167
 
168
 
169
  def download_video(url: str, dst: Path, password: str = "") -> Path:
170
- """Unified download entry point."""
171
  video_exts = (".mp4", ".mov", ".webm", ".mkv", ".avi", ".flv")
172
 
173
  if url.lower().endswith(video_exts):
@@ -188,12 +179,10 @@ def download_video(url: str, dst: Path, password: str = "") -> Path:
188
 
189
 
190
  def _encode_video_b64(path: Path) -> str:
191
- """Base64‑encode a file."""
192
  return base64.b64encode(path.read_bytes()).decode()
193
 
194
 
195
  def generate_report(video_path: Path, prompt: str, model_id: str, timeout: int = 300) -> str:
196
- """Send video + prompt to Gemini and return the response text."""
197
  b64 = _encode_video_b64(video_path)
198
  video_part = {"inline_data": {"mime_type": "video/mp4", "data": b64}}
199
  model = genai.GenerativeModel(model_name=model_id)
@@ -207,7 +196,6 @@ def generate_report(video_path: Path, prompt: str, model_id: str, timeout: int =
207
 
208
 
209
  def _strip_prompt_echo(prompt: str, text: str, threshold: float = 0.68) -> str:
210
- """Trim the prompt if the model repeats it at the start."""
211
  if not prompt or not text:
212
  return text
213
  clean_prompt = " ".join(prompt.lower().split())
@@ -222,7 +210,6 @@ def _strip_prompt_echo(prompt: str, text: str, threshold: float = 0.68) -> str:
222
  # UI helpers
223
  # ----------------------------------------------------------------------
224
  def _expand_sidebar(width: int = 380) -> None:
225
- """Make the Streamlit sidebar wider."""
226
  st.markdown(
227
  f"""
228
  <style>
@@ -235,11 +222,128 @@ def _expand_sidebar(width: int = 380) -> None:
235
  unsafe_allow_html=True,
236
  )
237
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  # ----------------------------------------------------------------------
239
  # Session‑state defaults
240
  # ----------------------------------------------------------------------
241
  def _init_state() -> None:
242
- """Populate Streamlit's session_state with sensible defaults."""
243
  defaults = {
244
  "url": "",
245
  "video_path": "",
@@ -253,23 +357,16 @@ def _init_state() -> None:
253
  "analysis_out": "",
254
  "raw_output": "",
255
  "last_error_detail": "",
256
- "show_raw_on_error": False,
257
- "show_analysis": False,
258
  }
259
  for k, v in defaults.items():
260
  st.session_state.setdefault(k, v)
261
 
262
 
263
  # ----------------------------------------------------------------------
264
- # Main entry point
265
  # ----------------------------------------------------------------------
266
  if __name__ == "__main__":
267
- # Initialise session state before any UI code runs
268
  _init_state()
269
-
270
- # Initialise the Gemini API – the key can be supplied via the sidebar or env var
271
  if st.session_state["api_key"]:
272
  genai.configure(api_key=st.session_state["api_key"])
273
-
274
- # Run the Streamlit app
275
  main()
 
44
  # Helper utilities
45
  # ----------------------------------------------------------------------
46
  def _sanitize_filename(url: str) -> str:
 
47
  name = Path(url).name.lower()
48
  return name.translate(str.maketrans("", "", string.punctuation)).replace(" ", "_")
49
 
50
 
51
  def _file_sha256(path: Path) -> Optional[str]:
 
52
  try:
53
  h = hashlib.sha256()
54
  with path.open("rb") as f:
 
60
 
61
 
62
  def _convert_to_mp4(src: Path) -> Path:
 
63
  dst = src.with_suffix(".mp4")
64
  if dst.exists():
65
  return dst
 
76
 
77
 
78
  def _compress_video(inp: Path, crf: int = 28, preset: str = "fast") -> Path:
 
79
  out = inp.with_name(f"{inp.stem}_compressed.mp4")
80
  try:
81
  ffmpeg.input(str(inp)).output(
 
87
 
88
 
89
  def _maybe_compress(path: Path, limit_mb: int) -> Tuple[Path, bool]:
 
90
  size_mb = path.stat().st_size / (1024 * 1024)
91
  if size_mb <= limit_mb:
92
  return path, False
 
94
 
95
 
96
  def _download_direct(url: str, dst: Path) -> Path:
 
97
  r = requests.get(url, stream=True, timeout=30)
98
  r.raise_for_status()
99
  out = dst / _sanitize_filename(url.split("/")[-1])
 
105
 
106
 
107
  def _download_with_yt_dlp(url: str, dst: Path, password: str = "") -> Path:
 
108
  tmpl = str(dst / "%(id)s.%(ext)s")
109
  fmt = "best[ext=mp4]/best"
110
 
 
149
  raise RuntimeError("No MP4 file was created.")
150
  newest = max(mp4_files, key=lambda p: p.stat().st_mtime)
151
 
 
152
  sha = _file_sha256(newest)
153
  if sha:
154
  for existing in dst.iterdir():
 
159
 
160
 
161
  def download_video(url: str, dst: Path, password: str = "") -> Path:
 
162
  video_exts = (".mp4", ".mov", ".webm", ".mkv", ".avi", ".flv")
163
 
164
  if url.lower().endswith(video_exts):
 
179
 
180
 
181
  def _encode_video_b64(path: Path) -> str:
 
182
  return base64.b64encode(path.read_bytes()).decode()
183
 
184
 
185
  def generate_report(video_path: Path, prompt: str, model_id: str, timeout: int = 300) -> str:
 
186
  b64 = _encode_video_b64(video_path)
187
  video_part = {"inline_data": {"mime_type": "video/mp4", "data": b64}}
188
  model = genai.GenerativeModel(model_name=model_id)
 
196
 
197
 
198
  def _strip_prompt_echo(prompt: str, text: str, threshold: float = 0.68) -> str:
 
199
  if not prompt or not text:
200
  return text
201
  clean_prompt = " ".join(prompt.lower().split())
 
210
  # UI helpers
211
  # ----------------------------------------------------------------------
212
  def _expand_sidebar(width: int = 380) -> None:
 
213
  st.markdown(
214
  f"""
215
  <style>
 
222
  unsafe_allow_html=True,
223
  )
224
 
225
+
226
+ # ----------------------------------------------------------------------
227
+ # Streamlit UI
228
+ # ----------------------------------------------------------------------
229
+ def main() -> None:
230
+ st.set_page_config(page_title="Video Analysis", layout="wide")
231
+ _expand_sidebar()
232
+
233
+ # ---------- Sidebar ----------
234
+ st.sidebar.header("Video Input")
235
+ st.sidebar.text_input("Video URL", key="url", placeholder="https://")
236
+
237
+ if st.sidebar.button("Load Video"):
238
+ try:
239
+ with st.spinner("Downloading video…"):
240
+ raw_path = download_video(
241
+ st.session_state["url"], DATA_DIR, st.session_state["video_password"]
242
+ )
243
+ mp4_path = _convert_to_mp4(Path(raw_path))
244
+ # Optional compression (if size exceeds user‑defined limit)
245
+ mp4_path, _ = _maybe_compress(mp4_path, st.session_state["compress_mb"])
246
+ st.session_state["video_path"] = str(mp4_path)
247
+ st.session_state["last_error"] = ""
248
+ st.toast("Video ready")
249
+ st.experimental_rerun()
250
+ except Exception as e:
251
+ st.session_state["last_error"] = f"Download failed: {e}"
252
+ st.sidebar.error(st.session_state["last_error"])
253
+
254
+ # ---------- Settings ----------
255
+ with st.sidebar.expander("Settings", expanded=False):
256
+ model = st.selectbox(
257
+ "Model", MODEL_OPTIONS, index=MODEL_OPTIONS.index(DEFAULT_MODEL)
258
+ )
259
+ if model == "custom":
260
+ model = st.text_input("Custom model ID", value=DEFAULT_MODEL, key="custom_model")
261
+ st.session_state["model_input"] = model
262
+
263
+ # API key handling
264
+ secret_key = os.getenv("GOOGLE_API_KEY", "")
265
+ if secret_key:
266
+ st.session_state["api_key"] = secret_key
267
+ st.text_input("Google API Key", key="api_key", type="password")
268
+
269
+ st.text_area(
270
+ "Analysis prompt",
271
+ value=DEFAULT_PROMPT,
272
+ key="prompt",
273
+ height=140,
274
+ )
275
+ st.text_input(
276
+ "Video password (if needed)",
277
+ key="video_password",
278
+ type="password",
279
+ )
280
+ st.number_input(
281
+ "Compress if > (MB)",
282
+ min_value=10,
283
+ max_value=2000,
284
+ value=st.session_state.get("compress_mb", 200),
285
+ step=10,
286
+ key="compress_mb",
287
+ )
288
+
289
+ if st.sidebar.button("Clear Video"):
290
+ for f in DATA_DIR.iterdir():
291
+ try:
292
+ f.unlink()
293
+ except Exception:
294
+ pass
295
+ st.session_state["video_path"] = ""
296
+ st.session_state["analysis_out"] = ""
297
+ st.session_state["raw_output"] = ""
298
+ st.toast("All cached videos cleared")
299
+ st.experimental_rerun()
300
+
301
+ # ---------- Main panel ----------
302
+ if st.session_state["last_error"]:
303
+ st.error(st.session_state["last_error"])
304
+
305
+ if st.session_state.get("video_path"):
306
+ st.video(st.session_state["video_path"])
307
+
308
+ if st.button("Run Analysis"):
309
+ st.session_state["busy"] = True
310
+ st.session_state["analysis_out"] = ""
311
+ st.session_state["raw_output"] = ""
312
+ try:
313
+ with st.spinner("Generating report…"):
314
+ raw = generate_report(
315
+ Path(st.session_state["video_path"]),
316
+ st.session_state["prompt"],
317
+ st.session_state["model_input"],
318
+ )
319
+ cleaned = _strip_prompt_echo(st.session_state["prompt"], raw)
320
+ st.session_state["analysis_out"] = cleaned
321
+ st.session_state["raw_output"] = raw
322
+ except Exception as e:
323
+ st.session_state["last_error"] = f"Analysis failed: {e}"
324
+ st.session_state["last_error_detail"] = traceback.format_exc()
325
+ finally:
326
+ st.session_state["busy"] = False
327
+ st.experimental_rerun()
328
+
329
+ # Show results
330
+ if st.session_state.get("analysis_out"):
331
+ st.subheader("📝 Analysis")
332
+ st.write(st.session_state["analysis_out"])
333
+
334
+ with st.expander("Show raw model output"):
335
+ st.code(st.session_state["raw_output"], language="text")
336
+
337
+ # Optional error details
338
+ if st.session_state.get("last_error_detail"):
339
+ with st.expander("Show error details"):
340
+ st.code(st.session_state["last_error_detail"], language="text")
341
+
342
+
343
  # ----------------------------------------------------------------------
344
  # Session‑state defaults
345
  # ----------------------------------------------------------------------
346
  def _init_state() -> None:
 
347
  defaults = {
348
  "url": "",
349
  "video_path": "",
 
357
  "analysis_out": "",
358
  "raw_output": "",
359
  "last_error_detail": "",
 
 
360
  }
361
  for k, v in defaults.items():
362
  st.session_state.setdefault(k, v)
363
 
364
 
365
  # ----------------------------------------------------------------------
366
+ # Entry point
367
  # ----------------------------------------------------------------------
368
  if __name__ == "__main__":
 
369
  _init_state()
 
 
370
  if st.session_state["api_key"]:
371
  genai.configure(api_key=st.session_state["api_key"])
 
 
372
  main()