- capture/: frame sampling at configurable fps, ffmpeg audio extraction (WAV, whisper-ready) - vision/: changed-pixel motion scoring, stable/moving segmentation, contour + perspective document detection, Laplacian sharpness scoring, optional CLAHE enhancement - pipeline: two-pass detect (motion timeline, then best-frame crop per stable window) writing crops, debug frames, report.json, and motion_scores.csv - CLI: probe / detect / extract-audio subcommands - config.yaml with tunable thresholds; placeholder packages for events, transcribe, ocr, naming, pdf, review_cli - synthetic sample video generator + 16 unit tests
45 lines
1.6 KiB
Python
45 lines
1.6 KiB
Python
import numpy as np
|
|
|
|
from paperpod.vision.motion import find_segments, motion_score, prepare_gray
|
|
|
|
|
|
def series(pattern: list[tuple[float, int]], dt: float = 0.125):
|
|
"""Expand [(score, count), ...] into (timestamps, scores)."""
|
|
scores = [s for s, n in pattern for _ in range(n)]
|
|
timestamps = [i * dt for i in range(len(scores))]
|
|
return timestamps, scores
|
|
|
|
|
|
def test_basic_stable_and_moving_segments():
|
|
# stable 2s, moving 1s, stable 2s
|
|
ts, sc = series([(0.5, 16), (10.0, 8), (0.5, 16)])
|
|
segs = find_segments(ts, sc, threshold=4.0, stable_min_duration_s=1.0)
|
|
kinds = [s.kind for s in segs]
|
|
assert kinds == ["stable", "moving", "stable"]
|
|
assert segs[0].duration >= 1.0
|
|
assert segs[2].duration >= 1.0
|
|
|
|
|
|
def test_short_stable_blip_is_demoted_to_moving():
|
|
# moving, tiny stable pause (0.25s), moving -> should merge into one moving segment
|
|
ts, sc = series([(10.0, 8), (0.5, 2), (10.0, 8)])
|
|
segs = find_segments(ts, sc, threshold=4.0, stable_min_duration_s=1.0)
|
|
assert [s.kind for s in segs] == ["moving"]
|
|
|
|
|
|
def test_empty_input():
|
|
assert find_segments([], [], threshold=4.0, stable_min_duration_s=1.0) == []
|
|
|
|
|
|
def test_motion_score_zero_for_identical_frames():
|
|
img = np.random.default_rng(0).integers(0, 255, (100, 100, 3), dtype=np.uint8)
|
|
g = prepare_gray(img)
|
|
assert motion_score(g, g) == 0.0
|
|
|
|
|
|
def test_motion_score_positive_for_changed_frames():
|
|
rng = np.random.default_rng(0)
|
|
a = prepare_gray(rng.integers(0, 255, (100, 100, 3), dtype=np.uint8))
|
|
b = prepare_gray(np.full((100, 100, 3), 200, dtype=np.uint8))
|
|
assert motion_score(a, b) > 0
|