Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ dvr-scan -i video.mp4 --start-time 00:35:52 --duration 00:05:00
```
</span>

* <b><pre>-fs num_frames, --frame-skip num_frames</pre></b> Number of frames to skip after processing a given frame. Improves performance, at expense of frame and time accuracy, and may increase probability of missing motion events. If set, `-l`/`--min-event-length`, `-tb`/`--time-before-event`, and `-tp`/`--time-post-event` will all be scaled relative to the source framerate. Values above 1 or 2 are not recommended.
* <b><pre>-fs num_frames, --frame-skip num_frames</pre></b> Number of frames to skip after processing a given frame. Improves performance, at expense of frame and time accuracy, and may increase probability of missing motion events. If set, `-l`/`--min-event-length`, `-Y`/`--max-event-length`, `-tb`/`--time-before-event`, and `-tp`/`--time-post-event` will all be scaled relative to the source framerate. Values above 1 or 2 are not recommended.
<br/><br/>When using output modes which re-encode processed frames (`encode`, `opencv`), skipped frames are not included. Set `-m`/`--output-mode` to `ffmpeg` or `copy` to include all frames from the input video when writing motion events to disk.
<br/><br/>Although adjusted for frame skipping, bounding box smoothing may be inconsistent when using frame skipping. Set `-bb 0` to disable smoothing.
<span class="dvr-scan-example">
Expand All @@ -199,6 +199,13 @@ All time values can be given as a timecode (`HH:MM:SS` or `HH:MM:SS.nnn`), in se
```
--min-event-length 2
```
</span>

* <b><pre>-l time, --max-event-length time</pre></b> Maximum amount of time/frames allowed into a single event.
<span class="dvr-scan-default">
```
--max-event-length 1000
```
</span>

* <b><pre>-tb time, --time-before-event time</pre></b> Maximum amount of time to include before each event.
Expand Down Expand Up @@ -515,6 +522,14 @@ All time values can be given as a timecode: (`HH:MM:SS` or `HH:MM:SS.nnn`), in s
```
</span>

* <b><pre>max-event-length</pre></b>
Maximum mount of time of a single event.
<span class="dvr-scan-default">
```
max-event-length = 1000
```
</span>

* <b><pre>time-before-event</pre></b>
Amount of time to include before a motion event.
<span class="dvr-scan-default">
Expand Down
6 changes: 6 additions & 0 deletions dvr-scan.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
# Amount of time which must have motion in each frame to trigger an event.
#min-event-length = 2

# Maximum amount of time of a single event.
#max-event-length = 1000

# Maximum amount of time of an event
#max-event-length = 999999

# Amount of time to include before an event.
#time-before-event = 1.5s

Expand Down
12 changes: 12 additions & 0 deletions dvr_scan/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ def get_cli_parser(user_config: ConfigRegistry):
),
)

parser.add_argument(
"-Y",
"--max-event-length",
metavar="time",
type=timecode_type_check("time"),
help=(
"Length of time that must contain motion before triggering a new event. Can be"
" specified as frames (123), seconds (12.3s), or timecode (00:00:01).%s"
% user_config.get_help_string("max-event-length")
),
)

parser.add_argument(
"-tb",
"--time-before-event",
Expand Down
1 change: 1 addition & 0 deletions dvr_scan/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ def from_config(config_value: str, default: "RGBValue") -> "RGBValue":
"max-events": 0,
"merge-window": MergeWindowValue(),
"min-event-length": TimecodeValue("0.1s"),
"max-event-length": TimecodeValue("999999s"),
"time-before-event": TimecodeValue("1.5s"),
"time-post-event": TimecodeValue("2.0s"),
"use-pts": False,
Expand Down
15 changes: 14 additions & 1 deletion dvr_scan/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def __init__(

# Motion Event Parameters (set_event_params)
self._min_event_len = None # -l/--min-event-length
self._max_event_len = None # -Y/--max-event-length
self._time_before_event = None # -tb/--time-before-event
self._time_post_event = None # -tp/--time-post-event
self._merge_window = None # --merge-window (None means auto: use -tp)
Expand Down Expand Up @@ -409,6 +410,7 @@ def set_regions(
def set_event_params(
self,
min_event_len: ty.Union[int, float, str] = "0.1s",
max_event_len: ty.Union[int, float, str] = "999999s",
time_pre_event: ty.Union[int, float, str] = "1.5s",
time_post_event: ty.Union[int, float, str] = "2s",
merge_window: ty.Optional[ty.Union[int, float, str]] = None,
Expand All @@ -419,6 +421,7 @@ def set_event_params(
A `merge_window` of None or "auto" uses the value of `time_post_event`."""
assert self._input.framerate is not None
self._min_event_len = FrameTimecode(min_event_len, self._input.framerate)
self._max_event_len = FrameTimecode(max_event_len, self._input.framerate)
self._time_before_event = FrameTimecode(time_pre_event, self._input.framerate)
self._post_event_len = FrameTimecode(time_post_event, self._input.framerate)
if merge_window is None or (
Expand Down Expand Up @@ -667,9 +670,11 @@ def scan(self) -> ty.Optional[DetectionResult]:
# from the average framerate, but emitted event boundaries are always exact (PTS-based).
pre_event_len: int = self._time_before_event.frame_num // (self._frame_skip + 1)
min_event_len: int = max(self._min_event_len.frame_num // (self._frame_skip + 1), 1)
max_event_len: int = max(self._max_event_len.frame_num // (self._frame_skip + 1), 1)

# Calculations below rely on min_event_len always being >= 1 (cannot be zero)
assert min_event_len >= 1, "min_event_len must be at least 1 frame"
assert max_event_len >= 1, "max_event_len must be at least 1 frame"

# Presentation duration of one source frame at the average framerate.
frame_duration: float = 1.0 / float(self._input.framerate)
Expand Down Expand Up @@ -804,6 +809,11 @@ def scan(self) -> ty.Optional[DetectionResult]:
or height_fraction > self._max_height
):
frame_score = 0
max_event_len_reached = False
if in_motion_event and event_start is not None:
if (frame.timecode.frame_num - event_start.frame_num) > max_event_len:
max_event_len_reached = True
frame_score = 0
above_threshold = frame_score >= self._threshold

event_window.append(frame_score)
Expand Down Expand Up @@ -867,7 +877,10 @@ def scan(self) -> ty.Optional[DetectionResult]:
post_motion_frames = []
# Otherwise, we wait until the merge window has passed before ending
# this motion event and start looking for a new one.
elif (frame.timecode.seconds - last_motion_time.seconds) >= merge_window_duration:
# If the event is longer than max_event_len, we end it immediately.
elif (
frame.timecode.seconds - last_motion_time.seconds
) >= merge_window_duration or max_event_len_reached:
in_motion_event = False

logger.debug("event %d high score %f" % (1 + len(event_list), self._highscore))
Expand Down
1 change: 1 addition & 0 deletions dvr_scan/shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def init_scanner(

scanner.set_event_params(
min_event_len=settings.get("min-event-length"),
max_event_len=settings.get("max-event-length"),
time_pre_event=settings.get("time-before-event"),
time_post_event=settings.get("time-post-event"),
merge_window=settings.get("merge-window"),
Expand Down
Loading