lucid-hf commited on
Commit
d70ea7c
·
verified ·
1 Parent(s): 1cf268e

CI: deploy Docker/PDM Space

Browse files
services/app_service/pages/bushland_beacon.py CHANGED
@@ -91,15 +91,15 @@ with st.sidebar:
91
 
92
  st.sidebar.header("Image Detection")
93
  img_file = st.file_uploader("Upload an image", type=["jpg", "jpeg", "png"], key="img_up")
94
- run_img = st.button("🔍 Run Image Detection", use_container_width=True)
95
 
96
  st.sidebar.header("Video")
97
  vid_file = st.file_uploader("Upload a video", type=["mp4", "mov", "avi", "mkv"], key="vid_up")
98
 
99
  # New buttons
100
- run_vid_plain = st.button("Play Video", use_container_width=True)
101
- run_vid = st.button("🎥 Run Detection", use_container_width=True)
102
- stop_vid = st.button("Stop", use_container_width=True)
103
 
104
  if stop_vid:
105
  st.session_state["stop_video"] = True
 
91
 
92
  st.sidebar.header("Image Detection")
93
  img_file = st.file_uploader("Upload an image", type=["jpg", "jpeg", "png"], key="img_up")
94
+ run_img = st.button("🔎 Detect", use_container_width=True)
95
 
96
  st.sidebar.header("Video")
97
  vid_file = st.file_uploader("Upload a video", type=["mp4", "mov", "avi", "mkv"], key="vid_up")
98
 
99
  # New buttons
100
+ run_vid_plain = st.button("▶️ Play", use_container_width=True)
101
+ run_vid = st.button("📽️ Detect", use_container_width=True)
102
+ stop_vid = st.button("🛑 Stop", use_container_width=True)
103
 
104
  if stop_vid:
105
  st.session_state["stop_video"] = True
services/app_service/pages/signal_watch.py CHANGED
@@ -16,7 +16,6 @@ from PIL import Image
16
  from utils.model_manager import get_model_manager, load_model
17
 
18
 
19
-
20
  def _image_to_data_url(path: str) -> str:
21
  p = Path(path)
22
  if not p.is_absolute():
@@ -40,8 +39,6 @@ model_manager = get_model_manager()
40
 
41
  # =============== Sidebar: custom menu + cards ===============
42
  with st.sidebar:
43
-
44
-
45
  logo_data_url = _image_to_data_url("../resources/images/lucid_insights_logo.png")
46
  st.markdown(
47
  f"""
@@ -59,8 +56,6 @@ with st.sidebar:
59
  st.markdown("---")
60
  st.page_link("pages/task_drone.py", label="Task Drone")
61
  st.page_link("pages/task_satellite.py", label="Task Satellite")
62
-
63
-
64
  st.markdown("---")
65
 
66
  # ---------- Sidebar: Video feed dropdown ----------
@@ -116,17 +111,19 @@ with st.sidebar:
116
  max_seconds = None # Always unlimited mode
117
  st.info("⚠️ Video Stream is running in unlimited mode — use Stop to end")
118
 
119
- col1, col2 = st.columns(2)
120
- with col1:
121
- run_detection = st.button("🎥 Run", use_container_width=True)
122
- with col2:
123
- stop_detection = st.button(
124
- "⏹️ Stop", use_container_width=True, help="Stop current detection"
125
- )
126
- st.sidebar.markdown("---")
 
 
127
 
128
  # Parameters card (shared)
129
- st.sidebar.header("Parameters")
130
  confidence_threshold = st.slider("Minimum Confidence", 0.0, 1.0, 0.5, 0.01)
131
 
132
  device = model_manager.device
@@ -146,13 +143,13 @@ with st.sidebar:
146
  default_stride,
147
  help="Higher values skip frames to keep inference responsive on slower hardware.",
148
  )
149
- st.sidebar.markdown("---")
150
  # Render model selection UI
151
  model_label, model_key = model_manager.render_model_selection(
152
  key_prefix="signal_watch"
153
  )
154
 
155
- st.sidebar.markdown("---")
156
  # Render device information
157
  model_manager.render_device_info()
158
 
@@ -195,28 +192,22 @@ else:
195
 
196
  def find_ytdlp_executable() -> str | None:
197
  """Find the yt-dlp executable, checking multiple possible locations."""
198
- # First try the system PATH
199
  ytdlp_path = shutil.which("yt-dlp")
200
  if ytdlp_path:
201
  return ytdlp_path
202
-
203
- # Try common virtual environment locations
204
  venv_paths = [
205
  Path(sys.executable).parent / "yt-dlp",
206
  Path(sys.executable).parent.parent / "bin" / "yt-dlp",
207
  Path(__file__).parents[3] / ".venv" / "bin" / "yt-dlp",
208
  Path(__file__).parents[3] / ".venv" / "Scripts" / "yt-dlp.exe", # Windows
209
  ]
210
-
211
  for path in venv_paths:
212
  if path.exists() and path.is_file():
213
  return str(path)
214
-
215
  return None
216
 
217
 
218
  def is_youtube_url(url: str) -> bool:
219
- """Check if the URL is a YouTube URL."""
220
  youtube_patterns = [
221
  r"(?:https?://)?(?:www\.)?youtube\.com/watch\?v=",
222
  r"(?:https?://)?(?:www\.)?youtu\.be/",
@@ -227,11 +218,9 @@ def is_youtube_url(url: str) -> bool:
227
 
228
 
229
  def check_ytdlp_available() -> bool:
230
- """Check if yt-dlp is available on the system."""
231
  ytdlp_path = find_ytdlp_executable()
232
  if not ytdlp_path:
233
  return False
234
-
235
  try:
236
  result = subprocess.run(
237
  [ytdlp_path, "--version"],
@@ -246,69 +235,46 @@ def check_ytdlp_available() -> bool:
246
 
247
 
248
  def is_live_youtube_stream(url: str) -> bool:
249
- """Check if a YouTube URL is a live stream."""
250
  if not is_youtube_url(url):
251
  return False
252
-
253
  ytdlp_path = find_ytdlp_executable()
254
  if not ytdlp_path:
255
  return False
256
-
257
  try:
258
- # Get video info to check if it's live
259
  cmd = [ytdlp_path, "--dump-json", "--no-playlist", "--no-warnings", url]
260
-
261
  result = subprocess.run(
262
  cmd, check=False, capture_output=True, text=True, timeout=15
263
  )
264
-
265
  if result.returncode == 0:
266
  import json
267
-
268
  video_info = json.loads(result.stdout)
269
  return video_info.get("is_live", False)
270
  except Exception:
271
  pass
272
-
273
  return False
274
 
275
 
276
  def extract_youtube_stream_url(url: str, cookies_path: str | None = None) -> str | None:
277
- """Extract the actual stream URL from a YouTube URL using yt-dlp."""
278
  ytdlp_path = find_ytdlp_executable()
279
  if not ytdlp_path:
280
  st.error("yt-dlp is not installed or not available in PATH.")
281
  st.info("Please install yt-dlp: `pip install yt-dlp` or `brew install yt-dlp`")
282
  return None
283
-
284
  if not check_ytdlp_available():
285
  st.error("yt-dlp found but not working properly.")
286
  return None
287
-
288
  try:
289
- # Show progress message
290
  with st.spinner("Extracting YouTube stream URL..."):
291
  format_options = ["best[height<=720]", "best[height<=480]", "best", "worst"]
292
-
293
- # First try with provided cookies if available
294
  if cookies_path and Path(cookies_path).exists():
295
  for fmt in format_options:
296
  cmd = [
297
- ytdlp_path,
298
- "--get-url",
299
- "--format",
300
- fmt,
301
- "--no-playlist",
302
- "--no-warnings",
303
- "--cookies",
304
- cookies_path,
305
- url,
306
  ]
307
-
308
  result = subprocess.run(
309
  cmd, check=False, capture_output=True, text=True, timeout=30
310
  )
311
-
312
  if result.returncode == 0:
313
  stream_url = result.stdout.strip()
314
  if stream_url and stream_url.startswith("http"):
@@ -316,14 +282,12 @@ def extract_youtube_stream_url(url: str, cookies_path: str | None = None) -> str
316
  return stream_url
317
  else:
318
  st.warning(f"yt-dlp failed with format {fmt}: {result.stderr}")
319
-
320
  except subprocess.TimeoutExpired:
321
  st.error("Timeout while extracting YouTube stream URL. The video might be unavailable.")
322
  except FileNotFoundError:
323
  st.error("yt-dlp not found. Please install it: pip install yt-dlp")
324
  except Exception as e:
325
  st.error(f"Error extracting YouTube stream URL: {e!s}")
326
-
327
  return None
328
 
329
 
@@ -386,8 +350,6 @@ def run_video_feed_detection(
386
  is_youtube = isinstance(cap_source, str) and is_youtube_url(cap_source)
387
  is_webcam = isinstance(cap_source, int)
388
 
389
- # OpenCV capture
390
- # On Windows, you may prefer: cv2.VideoCapture(cap_source, cv2.CAP_DSHOW)
391
  cap = cv2.VideoCapture(cap_source)
392
 
393
  # For webcam, set resolution & reduce buffering
@@ -396,7 +358,6 @@ def run_video_feed_detection(
396
  if webcam_size:
397
  cap.set(cv2.CAP_PROP_FRAME_WIDTH, webcam_size[0])
398
  cap.set(cv2.CAP_PROP_FRAME_HEIGHT, webcam_size[1])
399
- # Hints for common backends
400
  cap.set(cv2.CAP_PROP_FPS, 30)
401
  cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
402
 
@@ -404,28 +365,24 @@ def run_video_feed_detection(
404
  st.error("Failed to open the selected source.")
405
  return
406
 
407
- # Additional robustness for cloud deployment
408
  try:
409
- # Test if we can read at least one frame
410
  test_ret, test_frame = cap.read()
411
  if not test_ret or test_frame is None:
412
  st.error("Video source is not providing frames. Please try a different stream.")
413
  cap.release()
414
  return
415
- # Reset to beginning when it's a file
416
  cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
417
  except Exception as e:
418
  st.error(f"Error testing video source: {e}")
419
  cap.release()
420
  return
421
 
422
- # Set buffer size for live streams to reduce latency
423
  if is_youtube or isinstance(cap_source, str):
424
- cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # Minimal buffer for live streams
425
- cap.set(cv2.CAP_PROP_FPS, 25) # Target FPS
426
  cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
427
 
428
- # Check if it's a local file (not a URL)
429
  is_file = False
430
  if isinstance(cap_source, str):
431
  if cap_source.startswith(("http://", "https://", "rtsp://")):
@@ -445,7 +402,6 @@ def run_video_feed_detection(
445
  start_t = time.time()
446
  i = 0
447
  last_preview_update = 0
448
- # Use frame count-based preview throttling to prevent freezing
449
  preview_update_interval = max(1, int(30 / (preview_max_fps or 10))) # Update every N frames
450
 
451
  writer = None
@@ -460,7 +416,7 @@ def run_video_feed_detection(
460
  if not is_file and preview_max_fps and preview_max_fps <= 6:
461
  max_frame_skip = 15
462
  else:
463
- max_frame_skip = 5 # Limit how many buffered frames we drop at once
464
 
465
  progress_update_seconds = 0.0 if is_file else (
466
  0.7 if not preview_max_fps else max(0.4, 1.0 / preview_max_fps)
@@ -469,16 +425,14 @@ def run_video_feed_detection(
469
 
470
  try:
471
  while True:
472
- # Check for stop signal
473
  if st.session_state.get("stop_processing", False):
474
  st.info("🛑 Stopping detection...")
475
  break
476
 
477
  ok, frame = cap.read()
478
  if not ok:
479
- # For live streams, try to reconnect a few times before giving up
480
  if is_youtube or not is_file:
481
- if i < 10: # Only retry early in the stream
482
  time.sleep(0.5)
483
  continue
484
  break
@@ -488,7 +442,6 @@ def run_video_feed_detection(
488
  continue
489
 
490
  inference_start = time.time()
491
-
492
  if model_key == "deim" and hasattr(model, "annotate_frame_bgr"):
493
  vis = model.annotate_frame_bgr(frame, min_confidence=conf_thr)
494
  elif model_key == "deim":
@@ -501,9 +454,9 @@ def run_video_feed_detection(
501
  _, vis = model.predict_and_visualize(
502
  frame, min_confidence=conf_thr, show_score=True
503
  )
504
-
505
  inference_elapsed = time.time() - inference_start
506
 
 
507
  if not is_file and frame_interval > 0.0 and inference_elapsed > frame_interval:
508
  frames_to_drop = min(int(inference_elapsed / frame_interval) - 1, max_frame_skip)
509
  for _ in range(frames_to_drop):
@@ -512,7 +465,7 @@ def run_video_feed_detection(
512
 
513
  loop_now = time.time()
514
 
515
- # Update progress and status
516
  if is_file or (loop_now - last_progress_time) >= progress_update_seconds:
517
  progress = i / total if total > 0 else 0
518
  prog.progress(
@@ -521,24 +474,17 @@ def run_video_feed_detection(
521
  )
522
  last_progress_time = loop_now
523
 
524
- # Update preview (throttled to prevent freezing)
525
  if frame_ph is not None and (i - last_preview_update) >= preview_update_interval:
526
  try:
527
- # Convert to RGB and resize for better performance
528
  display_frame = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)
529
-
530
- # Resize large frames to improve performance for cloud deployment
531
  height, width = display_frame.shape[:2]
532
  if is_youtube or not is_file:
533
- # More aggressive resizing for live sources (incl. webcam) to prevent freezing
534
  if width > 720:
535
  scale = 720 / width
536
  new_width = 720
537
  new_height = int(height * scale)
538
- display_frame = cv2.resize(
539
- display_frame, (new_width, new_height),
540
- interpolation=cv2.INTER_LINEAR
541
- )
542
  elif width > 1280:
543
  scale = 1280 / width
544
  new_width = 1280
@@ -552,12 +498,10 @@ def run_video_feed_detection(
552
  channels="RGB",
553
  )
554
  last_preview_update = i
555
-
556
  except Exception as e:
557
- # If display fails, continue processing but log the error
558
- if i % 30 == 0: # Only show error every 30 frames to avoid spam
559
  st.warning(f"Display update failed: {e}")
560
- last_preview_update = i # Still update counter to prevent repeated failures
561
 
562
  if writer is not None:
563
  writer.write(vis)
@@ -569,7 +513,6 @@ def run_video_feed_detection(
569
  else:
570
  elapsed = time.time() - start_t
571
  stream_type = "YouTube" if is_youtube else ("Live" if not is_file else "File")
572
-
573
  if max_seconds is None:
574
  prog.progress(0.0, text=f"{stream_type}… {int(elapsed)}s (unlimited)")
575
  else:
@@ -580,18 +523,15 @@ def run_video_feed_detection(
580
  if elapsed >= max_seconds:
581
  return
582
 
583
- # For live streams, add adaptive delay based on processing speed
584
  if not is_file:
585
  elapsed = time.time() - start_t
586
  if elapsed > 0:
587
  current_fps = i / elapsed
588
- # Target 10–15 FPS for live streams to prevent freezing
589
  if current_fps > 15:
590
- time.sleep(0.1) # Slow down if too fast
591
  elif current_fps < 8:
592
- time.sleep(0.02) # Slight nudge
593
 
594
- # Clear GPU memory periodically for long-running streams
595
  if i % 100 == 0 and is_youtube:
596
  if torch.cuda.is_available():
597
  torch.cuda.empty_cache()
@@ -599,14 +539,11 @@ def run_video_feed_detection(
599
  except Exception as e:
600
  st.error(f"Error during processing: {e!s}")
601
  finally:
602
- # Ensure proper cleanup
603
  cap.release()
604
  if writer is not None:
605
  writer.release()
606
- # Clear GPU memory after processing
607
  if torch.cuda.is_available():
608
  torch.cuda.empty_cache()
609
- # Reset stop flag
610
  st.session_state.stop_processing = False
611
 
612
  stream_type = "YouTube" if is_youtube else ("Live" if not is_file else "File")
@@ -622,15 +559,76 @@ def run_video_feed_detection(
622
  )
623
 
624
 
625
- # Stop/Run controls
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
  if stop_detection:
627
  st.session_state.stop_processing = True
628
- st.info("🛑 Stop signal sent. Detection will stop after current frame.")
629
 
630
- if run_detection:
631
- # Reset stop flag when starting new detection
 
 
632
  st.session_state.stop_processing = False
 
 
633
 
 
 
634
  src = resolve_video_source(src_choice, cookies_path, webcam_index=webcam_index)
635
  # Note: 0 (webcam) is a valid source; don't treat it as falsey
636
  if src is None and src != 0:
@@ -646,6 +644,4 @@ if run_detection:
646
  webcam_size=webcam_size if src_choice == "Webcam" else None,
647
  )
648
  else:
649
- st.info(
650
- "Pick a video source from the sidebar (including **Webcam** or YouTube) and click **Run Detection**."
651
- )
 
16
  from utils.model_manager import get_model_manager, load_model
17
 
18
 
 
19
  def _image_to_data_url(path: str) -> str:
20
  p = Path(path)
21
  if not p.is_absolute():
 
39
 
40
  # =============== Sidebar: custom menu + cards ===============
41
  with st.sidebar:
 
 
42
  logo_data_url = _image_to_data_url("../resources/images/lucid_insights_logo.png")
43
  st.markdown(
44
  f"""
 
56
  st.markdown("---")
57
  st.page_link("pages/task_drone.py", label="Task Drone")
58
  st.page_link("pages/task_satellite.py", label="Task Satellite")
 
 
59
  st.markdown("---")
60
 
61
  # ---------- Sidebar: Video feed dropdown ----------
 
111
  max_seconds = None # Always unlimited mode
112
  st.info("⚠️ Video Stream is running in unlimited mode — use Stop to end")
113
 
114
+ # == Controls: Run / Stop / Play Only ==
115
+ c1, c2, c3 = st.columns(3)
116
+ with c1:
117
+ play_only = st.button("▶️ Play", use_container_width=True, help="Play raw video without detection")
118
+ with c2:
119
+ run_detection = st.button("🔎 Detect", use_container_width=True)
120
+ with c3:
121
+ stop_detection = st.button("🛑 Stop", use_container_width=True, help="Stop current stream")
122
+
123
+ st.markdown("---")
124
 
125
  # Parameters card (shared)
126
+ st.header("Parameters")
127
  confidence_threshold = st.slider("Minimum Confidence", 0.0, 1.0, 0.5, 0.01)
128
 
129
  device = model_manager.device
 
143
  default_stride,
144
  help="Higher values skip frames to keep inference responsive on slower hardware.",
145
  )
146
+ st.markdown("---")
147
  # Render model selection UI
148
  model_label, model_key = model_manager.render_model_selection(
149
  key_prefix="signal_watch"
150
  )
151
 
152
+ st.markdown("---")
153
  # Render device information
154
  model_manager.render_device_info()
155
 
 
192
 
193
  def find_ytdlp_executable() -> str | None:
194
  """Find the yt-dlp executable, checking multiple possible locations."""
 
195
  ytdlp_path = shutil.which("yt-dlp")
196
  if ytdlp_path:
197
  return ytdlp_path
 
 
198
  venv_paths = [
199
  Path(sys.executable).parent / "yt-dlp",
200
  Path(sys.executable).parent.parent / "bin" / "yt-dlp",
201
  Path(__file__).parents[3] / ".venv" / "bin" / "yt-dlp",
202
  Path(__file__).parents[3] / ".venv" / "Scripts" / "yt-dlp.exe", # Windows
203
  ]
 
204
  for path in venv_paths:
205
  if path.exists() and path.is_file():
206
  return str(path)
 
207
  return None
208
 
209
 
210
  def is_youtube_url(url: str) -> bool:
 
211
  youtube_patterns = [
212
  r"(?:https?://)?(?:www\.)?youtube\.com/watch\?v=",
213
  r"(?:https?://)?(?:www\.)?youtu\.be/",
 
218
 
219
 
220
  def check_ytdlp_available() -> bool:
 
221
  ytdlp_path = find_ytdlp_executable()
222
  if not ytdlp_path:
223
  return False
 
224
  try:
225
  result = subprocess.run(
226
  [ytdlp_path, "--version"],
 
235
 
236
 
237
  def is_live_youtube_stream(url: str) -> bool:
 
238
  if not is_youtube_url(url):
239
  return False
 
240
  ytdlp_path = find_ytdlp_executable()
241
  if not ytdlp_path:
242
  return False
 
243
  try:
 
244
  cmd = [ytdlp_path, "--dump-json", "--no-playlist", "--no-warnings", url]
 
245
  result = subprocess.run(
246
  cmd, check=False, capture_output=True, text=True, timeout=15
247
  )
 
248
  if result.returncode == 0:
249
  import json
 
250
  video_info = json.loads(result.stdout)
251
  return video_info.get("is_live", False)
252
  except Exception:
253
  pass
 
254
  return False
255
 
256
 
257
  def extract_youtube_stream_url(url: str, cookies_path: str | None = None) -> str | None:
 
258
  ytdlp_path = find_ytdlp_executable()
259
  if not ytdlp_path:
260
  st.error("yt-dlp is not installed or not available in PATH.")
261
  st.info("Please install yt-dlp: `pip install yt-dlp` or `brew install yt-dlp`")
262
  return None
 
263
  if not check_ytdlp_available():
264
  st.error("yt-dlp found but not working properly.")
265
  return None
 
266
  try:
 
267
  with st.spinner("Extracting YouTube stream URL..."):
268
  format_options = ["best[height<=720]", "best[height<=480]", "best", "worst"]
 
 
269
  if cookies_path and Path(cookies_path).exists():
270
  for fmt in format_options:
271
  cmd = [
272
+ ytdlp_path, "--get-url", "--format", fmt, "--no-playlist",
273
+ "--no-warnings", "--cookies", cookies_path, url,
 
 
 
 
 
 
 
274
  ]
 
275
  result = subprocess.run(
276
  cmd, check=False, capture_output=True, text=True, timeout=30
277
  )
 
278
  if result.returncode == 0:
279
  stream_url = result.stdout.strip()
280
  if stream_url and stream_url.startswith("http"):
 
282
  return stream_url
283
  else:
284
  st.warning(f"yt-dlp failed with format {fmt}: {result.stderr}")
 
285
  except subprocess.TimeoutExpired:
286
  st.error("Timeout while extracting YouTube stream URL. The video might be unavailable.")
287
  except FileNotFoundError:
288
  st.error("yt-dlp not found. Please install it: pip install yt-dlp")
289
  except Exception as e:
290
  st.error(f"Error extracting YouTube stream URL: {e!s}")
 
291
  return None
292
 
293
 
 
350
  is_youtube = isinstance(cap_source, str) and is_youtube_url(cap_source)
351
  is_webcam = isinstance(cap_source, int)
352
 
 
 
353
  cap = cv2.VideoCapture(cap_source)
354
 
355
  # For webcam, set resolution & reduce buffering
 
358
  if webcam_size:
359
  cap.set(cv2.CAP_PROP_FRAME_WIDTH, webcam_size[0])
360
  cap.set(cv2.CAP_PROP_FRAME_HEIGHT, webcam_size[1])
 
361
  cap.set(cv2.CAP_PROP_FPS, 30)
362
  cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
363
 
 
365
  st.error("Failed to open the selected source.")
366
  return
367
 
 
368
  try:
 
369
  test_ret, test_frame = cap.read()
370
  if not test_ret or test_frame is None:
371
  st.error("Video source is not providing frames. Please try a different stream.")
372
  cap.release()
373
  return
 
374
  cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
375
  except Exception as e:
376
  st.error(f"Error testing video source: {e}")
377
  cap.release()
378
  return
379
 
 
380
  if is_youtube or isinstance(cap_source, str):
381
+ cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
382
+ cap.set(cv2.CAP_PROP_FPS, 25)
383
  cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
384
 
385
+ # Determine if local file
386
  is_file = False
387
  if isinstance(cap_source, str):
388
  if cap_source.startswith(("http://", "https://", "rtsp://")):
 
402
  start_t = time.time()
403
  i = 0
404
  last_preview_update = 0
 
405
  preview_update_interval = max(1, int(30 / (preview_max_fps or 10))) # Update every N frames
406
 
407
  writer = None
 
416
  if not is_file and preview_max_fps and preview_max_fps <= 6:
417
  max_frame_skip = 15
418
  else:
419
+ max_frame_skip = 5
420
 
421
  progress_update_seconds = 0.0 if is_file else (
422
  0.7 if not preview_max_fps else max(0.4, 1.0 / preview_max_fps)
 
425
 
426
  try:
427
  while True:
 
428
  if st.session_state.get("stop_processing", False):
429
  st.info("🛑 Stopping detection...")
430
  break
431
 
432
  ok, frame = cap.read()
433
  if not ok:
 
434
  if is_youtube or not is_file:
435
+ if i < 10:
436
  time.sleep(0.5)
437
  continue
438
  break
 
442
  continue
443
 
444
  inference_start = time.time()
 
445
  if model_key == "deim" and hasattr(model, "annotate_frame_bgr"):
446
  vis = model.annotate_frame_bgr(frame, min_confidence=conf_thr)
447
  elif model_key == "deim":
 
454
  _, vis = model.predict_and_visualize(
455
  frame, min_confidence=conf_thr, show_score=True
456
  )
 
457
  inference_elapsed = time.time() - inference_start
458
 
459
+ # Drop buffered frames if we're slower than source FPS
460
  if not is_file and frame_interval > 0.0 and inference_elapsed > frame_interval:
461
  frames_to_drop = min(int(inference_elapsed / frame_interval) - 1, max_frame_skip)
462
  for _ in range(frames_to_drop):
 
465
 
466
  loop_now = time.time()
467
 
468
+ # Progress
469
  if is_file or (loop_now - last_progress_time) >= progress_update_seconds:
470
  progress = i / total if total > 0 else 0
471
  prog.progress(
 
474
  )
475
  last_progress_time = loop_now
476
 
477
+ # Preview (throttled)
478
  if frame_ph is not None and (i - last_preview_update) >= preview_update_interval:
479
  try:
 
480
  display_frame = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)
 
 
481
  height, width = display_frame.shape[:2]
482
  if is_youtube or not is_file:
 
483
  if width > 720:
484
  scale = 720 / width
485
  new_width = 720
486
  new_height = int(height * scale)
487
+ display_frame = cv2.resize(display_frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
 
 
 
488
  elif width > 1280:
489
  scale = 1280 / width
490
  new_width = 1280
 
498
  channels="RGB",
499
  )
500
  last_preview_update = i
 
501
  except Exception as e:
502
+ if i % 30 == 0:
 
503
  st.warning(f"Display update failed: {e}")
504
+ last_preview_update = i
505
 
506
  if writer is not None:
507
  writer.write(vis)
 
513
  else:
514
  elapsed = time.time() - start_t
515
  stream_type = "YouTube" if is_youtube else ("Live" if not is_file else "File")
 
516
  if max_seconds is None:
517
  prog.progress(0.0, text=f"{stream_type}… {int(elapsed)}s (unlimited)")
518
  else:
 
523
  if elapsed >= max_seconds:
524
  return
525
 
 
526
  if not is_file:
527
  elapsed = time.time() - start_t
528
  if elapsed > 0:
529
  current_fps = i / elapsed
 
530
  if current_fps > 15:
531
+ time.sleep(0.1)
532
  elif current_fps < 8:
533
+ time.sleep(0.02)
534
 
 
535
  if i % 100 == 0 and is_youtube:
536
  if torch.cuda.is_available():
537
  torch.cuda.empty_cache()
 
539
  except Exception as e:
540
  st.error(f"Error during processing: {e!s}")
541
  finally:
 
542
  cap.release()
543
  if writer is not None:
544
  writer.release()
 
545
  if torch.cuda.is_available():
546
  torch.cuda.empty_cache()
 
547
  st.session_state.stop_processing = False
548
 
549
  stream_type = "YouTube" if is_youtube else ("Live" if not is_file else "File")
 
559
  )
560
 
561
 
562
+ # === NEW: play-only (no detection) ===
563
+ def play_video_only(src_choice: str, webcam_index: int, webcam_size: tuple[int, int]):
564
+ """
565
+ Play raw video without running any model.
566
+ - For YouTube/live or local files: st.video(...)
567
+ - For Webcam: OpenCV capture loop until Stop pressed.
568
+ """
569
+ # YouTube/live or local file -> use st.video for simplest playback
570
+ if src_choice in youtube_urls:
571
+ st.info("▶ Playing YouTube stream (no detection). Use ⏹️ Stop to end.")
572
+ st.video(youtube_urls[src_choice])
573
+ return
574
+ if src_choice in video_map:
575
+ st.info("▶ Playing local file (no detection). Use ⏹️ Stop to end.")
576
+ st.video(video_map[src_choice])
577
+ return
578
+
579
+ # Webcam fallback -> simple OpenCV loop
580
+ if src_choice == "Webcam":
581
+ st.info("▶ Playing webcam (no detection). Use ⏹️ Stop to end.")
582
+ ph = st.empty()
583
+ cap = cv2.VideoCapture(int(webcam_index))
584
+ cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
585
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, webcam_size[0])
586
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, webcam_size[1])
587
+ cap.set(cv2.CAP_PROP_FPS, 30)
588
+ cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
589
+ if not cap.isOpened():
590
+ st.error("Failed to open webcam.")
591
+ return
592
+ try:
593
+ while True:
594
+ if st.session_state.get("stop_processing", False):
595
+ st.info("🛑 Stopping playback...")
596
+ break
597
+ ok, frame = cap.read()
598
+ if not ok:
599
+ time.sleep(0.05)
600
+ continue
601
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
602
+ ph.image(frame_rgb, channels="RGB", use_container_width=True, output_format="JPEG")
603
+ time.sleep(0.01)
604
+ except Exception as e:
605
+ st.error(f"Playback error: {e!s}")
606
+ finally:
607
+ cap.release()
608
+ st.session_state.stop_processing = False
609
+ st.success("Stopped webcam playback.")
610
+ else:
611
+ st.warning("Please choose a valid source to play.")
612
+
613
+
614
+ # Stop/Run/Play controls
615
+ if 'stop_processing' not in st.session_state:
616
+ st.session_state.stop_processing = False
617
+
618
  if stop_detection:
619
  st.session_state.stop_processing = True
620
+ st.info("🛑 Stop signal sent. Playback/detection will stop after current frame.")
621
 
622
+ if 'play_only' not in locals():
623
+ play_only = False # safety for environments that re-run blocks
624
+
625
+ if play_only:
626
  st.session_state.stop_processing = False
627
+ # Use *display* URLs for simple playback (no extraction/model)
628
+ play_video_only(src_choice, webcam_index=webcam_index, webcam_size=webcam_size)
629
 
630
+ elif 'run_detection' in locals() and run_detection:
631
+ st.session_state.stop_processing = False
632
  src = resolve_video_source(src_choice, cookies_path, webcam_index=webcam_index)
633
  # Note: 0 (webcam) is a valid source; don't treat it as falsey
634
  if src is None and src != 0:
 
644
  webcam_size=webcam_size if src_choice == "Webcam" else None,
645
  )
646
  else:
647
+ st.info("Pick a source from the sidebar and click **🎥 Run** for detections or **▶ Play Only** for raw playback.")
 
 
services/app_service/resources/cookies/www.youtube.com_cookies (1).txt CHANGED
@@ -14,13 +14,13 @@
14
  .youtube.com TRUE / TRUE 1795860052 __Secure-1PSID g.a0002wgrpcW9K-QNYioDxFejw3GZD9efUs0mRhFq3acAXjra01COElc9yIf8E5lZjcmehkOAoAACgYKATYSARUSFQHGX2MiWlVwx7WCXoMqEpE8OPe6uBoVAUF8yKoNXCEYEM1OaXvtmwgRgVk50076
15
  .youtube.com TRUE / TRUE 1795860052 __Secure-3PSID g.a0002wgrpcW9K-QNYioDxFejw3GZD9efUs0mRhFq3acAXjra01COIiAEqiDC2XZPFcs_RH2U7AACgYKAfYSARUSFQHGX2Mi0KLOQQs0Au5u5xYXwmfaBhoVAUF8yKqN132DDK9He9JDLUX1bEz10076
16
  .youtube.com TRUE / TRUE 1795860053 LOGIN_INFO AFmmF2swRAIgRKXvUyDr8P18ZQwmyUgxyKGvHjNBMkeIsGSaWm76WDACIG2OvtxNgjffm-ENPsSwdsv6sn1Rv3OLmUXY8uVhCzEF:QUQ3MjNmeWxRMkNvOEtZejh3VEVRRGxlazNMY3hEa0Zhc0dUcl9vTlVFZWtDdk5PX2laWWpvLWE0WngxbW9oYlNQRzFackoxeUpmTkx5SGtsWW9kUXl4eFo1ZUphTVFiYkhlRGxsTksyUUZkRnBBbTNDWWFVLUdlS09QaUVyUU1SVEtVOVNaOElJSFl5X2JPS09TT2JSQkswelFSaW43TVln
17
- .youtube.com TRUE / FALSE 1793705713 SIDCC AKEyXzVXwX8w2nWZfMv3_vm9x9MHP2X74I7xRSXBl1PVMH2De19pI_YA8z_2yr2Z13gIzUFOo2M
18
- .youtube.com TRUE / TRUE 1793705713 __Secure-1PSIDCC AKEyXzUsuWKIzQPgtFuXQQAbPdNspeAjdQuuPAxJ04FKw7fjyfwCLoBHSyjJ9CDr5INOHx6ysQ
19
- .youtube.com TRUE / TRUE 1793705713 __Secure-3PSIDCC AKEyXzXNYjyFEUljVmMwnMpTI-0eOyZ2rB2GIe7AxTqYYmrEa03vXkT9sGPwS7WMC-solvhrKcU
20
  .youtube.com TRUE / TRUE 0 YSC JNOzy0pmT_0
21
- .youtube.com TRUE / TRUE 1777721713 VISITOR_INFO1_LIVE y1BPxdbbDNs
22
- .youtube.com TRUE / TRUE 1777721713 VISITOR_PRIVACY_METADATA CgJBVRIEGgAgJw%3D%3D
23
  .youtube.com TRUE / TRUE 1777706424 __Secure-ROLLOUT_TOKEN CMDh2N6P_q_XIBDpoLH5yLyQAxjNjY3kuNWQAw%3D%3D
24
- .youtube.com TRUE / TRUE 1825241713 __Secure-YT_TVFAS t=489249&s=2
25
- .youtube.com TRUE / TRUE 1777721713 DEVICE_INFO ChxOelUyTlRBNE56VXlNek0xTnpBek16QTVOdz09EPGmosgGGIWu8scG
26
- .youtube.com TRUE /tv TRUE 1795001713 __Secure-YT_DERP CPjNrY5_
 
14
  .youtube.com TRUE / TRUE 1795860052 __Secure-1PSID g.a0002wgrpcW9K-QNYioDxFejw3GZD9efUs0mRhFq3acAXjra01COElc9yIf8E5lZjcmehkOAoAACgYKATYSARUSFQHGX2MiWlVwx7WCXoMqEpE8OPe6uBoVAUF8yKoNXCEYEM1OaXvtmwgRgVk50076
15
  .youtube.com TRUE / TRUE 1795860052 __Secure-3PSID g.a0002wgrpcW9K-QNYioDxFejw3GZD9efUs0mRhFq3acAXjra01COIiAEqiDC2XZPFcs_RH2U7AACgYKAfYSARUSFQHGX2Mi0KLOQQs0Au5u5xYXwmfaBhoVAUF8yKqN132DDK9He9JDLUX1bEz10076
16
  .youtube.com TRUE / TRUE 1795860053 LOGIN_INFO AFmmF2swRAIgRKXvUyDr8P18ZQwmyUgxyKGvHjNBMkeIsGSaWm76WDACIG2OvtxNgjffm-ENPsSwdsv6sn1Rv3OLmUXY8uVhCzEF:QUQ3MjNmeWxRMkNvOEtZejh3VEVRRGxlazNMY3hEa0Zhc0dUcl9vTlVFZWtDdk5PX2laWWpvLWE0WngxbW9oYlNQRzFackoxeUpmTkx5SGtsWW9kUXl4eFo1ZUphTVFiYkhlRGxsTksyUUZkRnBBbTNDWWFVLUdlS09QaUVyUU1SVEtVOVNaOElJSFl5X2JPS09TT2JSQkswelFSaW43TVln
17
+ .youtube.com TRUE / FALSE 1793708909 SIDCC AKEyXzVbBAWpzyxngGYEjLYfhkCBFrkwJ6n7kwoUUOKXL7xvBcjg97pGTxuIXPkvu3QNrmzPuuc
18
+ .youtube.com TRUE / TRUE 1793708909 __Secure-1PSIDCC AKEyXzUh-hV97EvC1uklo2cTzWzL5ckOLmqxL8TXM1gyzisAEwG0OVSD01JyDncI8DQuCpHomQ
19
+ .youtube.com TRUE / TRUE 1793708909 __Secure-3PSIDCC AKEyXzUcK9Z5Rr5cU66egaTKxwLaUIsHQs_6__IEWxDGFhtEtUOvZktYMtlCxq-rul6__UsHYyE
20
  .youtube.com TRUE / TRUE 0 YSC JNOzy0pmT_0
21
+ .youtube.com TRUE / TRUE 1777724909 VISITOR_INFO1_LIVE y1BPxdbbDNs
22
+ .youtube.com TRUE / TRUE 1777724909 VISITOR_PRIVACY_METADATA CgJBVRIEGgAgJw%3D%3D
23
  .youtube.com TRUE / TRUE 1777706424 __Secure-ROLLOUT_TOKEN CMDh2N6P_q_XIBDpoLH5yLyQAxjNjY3kuNWQAw%3D%3D
24
+ .youtube.com TRUE / TRUE 1825244909 __Secure-YT_TVFAS t=489249&s=2
25
+ .youtube.com TRUE / TRUE 1777724909 DEVICE_INFO ChxOelUyTlRBNE56VXlNek0xTnpBek16QTVOdz09EO2/osgGGIWu8scG
26
+ .youtube.com TRUE /tv TRUE 1795004909 __Secure-YT_DERP CPjNrY5_