Mateo commited on
Commit
4b21165
·
1 Parent(s): 7ba6cc4

extraction 1 process

Browse files
Files changed (2) hide show
  1. app.py +100 -68
  2. docker-compose.yml +1 -1
app.py CHANGED
@@ -40,7 +40,7 @@ DEFAULT_SPLIT_CFG = {
40
  "jump_meanabs_threshold": 18.0,
41
  "progress_every": 0,
42
  }
43
- ENABLE_MOTION_SEGMENTATION = os.getenv("ENABLE_MOTION_SEGMENTATION", "1").strip().lower() in {
44
  "1",
45
  "true",
46
  "yes",
@@ -102,18 +102,6 @@ def _format_idx_list(indices, max_items=40):
102
  return f"{head} ... {tail} (len={len(values)})"
103
 
104
 
105
- def _format_float_list(values, max_items=12, precision=3):
106
- if not values:
107
- return "[]"
108
- vals = [float(v) for v in values]
109
- if len(vals) <= max_items:
110
- return "[" + ", ".join(f"{v:.{precision}f}" for v in vals) + "]"
111
- half = max_items // 2
112
- head = ", ".join(f"{v:.{precision}f}" for v in vals[:half])
113
- tail = ", ".join(f"{v:.{precision}f}" for v in vals[-half:])
114
- return f"[{head}, ... {tail}] (len={len(vals)})"
115
-
116
-
117
  def _sample_uniform_items(items, n):
118
  n = max(1, int(n))
119
  if len(items) <= n:
@@ -243,15 +231,38 @@ def _probe_duration_ffprobe(video_path):
243
  return float(duration)
244
 
245
 
246
- def _sample_timestamps(duration, n):
247
- if duration is None or duration <= 0:
248
- return []
249
- n = max(1, int(n))
250
- if n == 1:
251
- return [0.0]
252
- # Stay slightly below duration to avoid requesting a frame past the stream end.
253
- max_t = max(0.0, float(duration) - 1e-3)
254
- return np.linspace(0.0, max_t, n).tolist()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
 
257
  def _extract_bgr_with_ffmpeg_disk(video_path, n):
@@ -351,62 +362,83 @@ def _extract_bgr_with_ffmpeg(video_path, n):
351
  _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
352
  return frames
353
 
354
- with timer("sample_timestamps", timing):
355
- timestamps = _sample_timestamps(duration, n)
356
- if not timestamps:
 
 
 
357
  timing["wall"] = time.perf_counter() - wall_t0
358
  _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
359
- return []
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  LOGGER.info(
362
- (
363
- "Frame extraction (memory/timestamps) | video=%s duration=%.3fs n_samples=%d "
364
- "sampled_timestamps=%s"
365
- ),
366
  video_name,
367
  duration,
368
- len(timestamps),
369
- _format_float_list(timestamps, max_items=10, precision=2),
 
 
370
  )
371
 
372
- frames = []
373
- for ts in timestamps:
374
- cmd = [
375
- ffmpeg,
376
- "-hide_banner",
377
- "-loglevel",
378
- "error",
379
- "-ss",
380
- f"{float(ts):.6f}",
381
- "-i",
382
- video_path,
383
- "-frames:v",
384
- "1",
385
- "-f",
386
- "image2pipe",
387
- "-vcodec",
388
- "mjpeg",
389
- "-",
390
- ]
391
- with timer("ffmpeg_extract_one", timing):
392
- proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
393
- if proc.returncode != 0 or not proc.stdout:
394
- LOGGER.warning(
395
- "Frame extraction timestamp failed | video=%s ts=%.3fs err=%s",
396
- video_name,
397
- float(ts),
398
- (proc.stderr.decode("utf-8", errors="ignore").strip() if proc.stderr else "no stderr"),
399
- )
400
- continue
 
401
 
402
- with timer("decode_one", timing):
403
- encoded = np.frombuffer(proc.stdout, dtype=np.uint8)
404
- frame = cv2.imdecode(encoded, cv2.IMREAD_COLOR)
405
- if frame is not None:
406
- frames.append(frame)
 
 
 
 
 
 
407
 
408
  if not frames:
409
- LOGGER.warning("Frame extraction | timestamp mode returned 0 frame, fallback to disk extraction")
410
  with timer("fallback_disk_extract", timing):
411
  frames = _extract_bgr_with_ffmpeg_disk(video_path, n)
412
 
@@ -414,7 +446,7 @@ def _extract_bgr_with_ffmpeg(video_path, n):
414
  "Frame extraction done | video=%s extracted=%d requested=%d",
415
  video_name,
416
  len(frames),
417
- len(timestamps),
418
  )
419
  timing["wall"] = time.perf_counter() - wall_t0
420
  _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
 
40
  "jump_meanabs_threshold": 18.0,
41
  "progress_every": 0,
42
  }
43
+ ENABLE_MOTION_SEGMENTATION = os.getenv("ENABLE_MOTION_SEGMENTATION", "0").strip().lower() in {
44
  "1",
45
  "true",
46
  "yes",
 
102
  return f"{head} ... {tail} (len={len(values)})"
103
 
104
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  def _sample_uniform_items(items, n):
106
  n = max(1, int(n))
107
  if len(items) <= n:
 
231
  return float(duration)
232
 
233
 
234
+ def _probe_video_size_ffprobe(video_path):
235
+ ffprobe = shutil.which("ffprobe")
236
+ if ffprobe is None:
237
+ return None
238
+
239
+ cmd = [
240
+ ffprobe,
241
+ "-v",
242
+ "error",
243
+ "-select_streams",
244
+ "v:0",
245
+ "-show_entries",
246
+ "stream=width,height",
247
+ "-of",
248
+ "csv=p=0:s=x",
249
+ video_path,
250
+ ]
251
+ proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False)
252
+ if proc.returncode != 0:
253
+ return None
254
+
255
+ line = next((txt.strip() for txt in proc.stdout.splitlines() if txt.strip()), "")
256
+ if "x" not in line:
257
+ return None
258
+ left, right = line.split("x", 1)
259
+ if not left.isdigit() or not right.isdigit():
260
+ return None
261
+
262
+ width, height = int(left), int(right)
263
+ if width <= 0 or height <= 0:
264
+ return None
265
+ return width, height
266
 
267
 
268
  def _extract_bgr_with_ffmpeg_disk(video_path, n):
 
362
  _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
363
  return frames
364
 
365
+ with timer("probe_video_size", timing):
366
+ video_size = _probe_video_size_ffprobe(video_path)
367
+ if video_size is None:
368
+ LOGGER.warning("Frame extraction | ffprobe size unavailable, fallback to disk extraction")
369
+ with timer("fallback_disk_extract", timing):
370
+ frames = _extract_bgr_with_ffmpeg_disk(video_path, n)
371
  timing["wall"] = time.perf_counter() - wall_t0
372
  _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
373
+ return frames
374
+
375
+ width, height = video_size
376
+ frame_size = int(width) * int(height) * 3
377
+ if frame_size <= 0:
378
+ LOGGER.warning("Frame extraction | invalid frame size, fallback to disk extraction")
379
+ with timer("fallback_disk_extract", timing):
380
+ frames = _extract_bgr_with_ffmpeg_disk(video_path, n)
381
+ timing["wall"] = time.perf_counter() - wall_t0
382
+ _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
383
+ return frames
384
+
385
+ sample_fps = max(1e-6, float(n) / float(duration))
386
 
387
  LOGGER.info(
388
+ "Frame extraction (single ffmpeg/rawvideo) | video=%s duration=%.3fs n_samples=%d fps=%.6f size=%dx%d",
 
 
 
389
  video_name,
390
  duration,
391
+ n,
392
+ sample_fps,
393
+ width,
394
+ height,
395
  )
396
 
397
+ cmd = [
398
+ ffmpeg,
399
+ "-hide_banner",
400
+ "-loglevel",
401
+ "error",
402
+ "-i",
403
+ video_path,
404
+ "-vf",
405
+ f"fps={sample_fps:.8f}",
406
+ "-frames:v",
407
+ str(n),
408
+ "-f",
409
+ "rawvideo",
410
+ "-pix_fmt",
411
+ "bgr24",
412
+ "-",
413
+ ]
414
+ with timer("ffmpeg_extract_rawvideo", timing):
415
+ proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
416
+ if proc.returncode != 0 or not proc.stdout:
417
+ LOGGER.warning(
418
+ "Frame extraction rawvideo failed | video=%s err=%s",
419
+ video_name,
420
+ (proc.stderr.decode("utf-8", errors="ignore").strip() if proc.stderr else "no stderr"),
421
+ )
422
+ with timer("fallback_disk_extract", timing):
423
+ frames = _extract_bgr_with_ffmpeg_disk(video_path, n)
424
+ timing["wall"] = time.perf_counter() - wall_t0
425
+ _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
426
+ return frames
427
 
428
+ with timer("decode_rawvideo", timing):
429
+ raw = proc.stdout
430
+ frame_count = len(raw) // frame_size
431
+ usable_bytes = frame_count * frame_size
432
+ if frame_count > 0 and usable_bytes:
433
+ arr = np.frombuffer(raw[:usable_bytes], dtype=np.uint8).reshape(frame_count, height, width, 3)
434
+ frames = [arr[idx].copy() for idx in range(frame_count)]
435
+ else:
436
+ frames = []
437
+ if len(frames) > n:
438
+ frames = _sample_uniform_items(frames, n)
439
 
440
  if not frames:
441
+ LOGGER.warning("Frame extraction | rawvideo mode returned 0 frame, fallback to disk extraction")
442
  with timer("fallback_disk_extract", timing):
443
  frames = _extract_bgr_with_ffmpeg_disk(video_path, n)
444
 
 
446
  "Frame extraction done | video=%s extracted=%d requested=%d",
447
  video_name,
448
  len(frames),
449
+ n,
450
  )
451
  timing["wall"] = time.perf_counter() - wall_t0
452
  _log_timing_summary(f"Frame extraction ({video_name})", timing, wall_time=timing["wall"])
docker-compose.yml CHANGED
@@ -10,7 +10,7 @@ services:
10
  STREAMLIT_SERVER_ADDRESS: "0.0.0.0"
11
  STREAMLIT_SERVER_PORT: "7860"
12
  STREAMLIT_BROWSER_GATHER_USAGE_STATS: "false"
13
- ENABLE_MOTION_SEGMENTATION: "1"
14
  FAST_N_SAMPLES: "12"
15
  INFER_BATCH_SIZE: "16"
16
  MODEL_IMGSZ: "1024"
 
10
  STREAMLIT_SERVER_ADDRESS: "0.0.0.0"
11
  STREAMLIT_SERVER_PORT: "7860"
12
  STREAMLIT_BROWSER_GATHER_USAGE_STATS: "false"
13
+ ENABLE_MOTION_SEGMENTATION: "0"
14
  FAST_N_SAMPLES: "12"
15
  INFER_BATCH_SIZE: "16"
16
  MODEL_IMGSZ: "1024"