DVR-Scan-Headless 1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. dvr_scan/LICENSE +47 -0
  2. dvr_scan/__init__.py +55 -0
  3. dvr_scan/__main__.py +80 -0
  4. dvr_scan/app/__init__.py +10 -0
  5. dvr_scan/app/__main__.py +218 -0
  6. dvr_scan/app/about_window.py +137 -0
  7. dvr_scan/app/application.py +1700 -0
  8. dvr_scan/app/common.py +31 -0
  9. dvr_scan/app/region_editor.py +1416 -0
  10. dvr_scan/app/scan_window.py +258 -0
  11. dvr_scan/app/widgets.py +213 -0
  12. dvr_scan/cli.py +511 -0
  13. dvr_scan/config.py +607 -0
  14. dvr_scan/controller.py +215 -0
  15. dvr_scan/detector.py +124 -0
  16. dvr_scan/docs/404.html +1 -0
  17. dvr_scan/docs/assets/javascripts/bundle.88dd0f4e.min.js +15 -0
  18. dvr_scan/docs/assets/stylesheets/main.6f8fc17f.min.css +1 -0
  19. dvr_scan/docs/assets/stylesheets/palette.06af60db.min.css +1 -0
  20. dvr_scan/docs/changelog.html +1 -0
  21. dvr_scan/docs/docs.html +73 -0
  22. dvr_scan/docs/faq.html +13 -0
  23. dvr_scan/docs/guide.html +1 -0
  24. dvr_scan/docs/index.html +5 -0
  25. dvr_scan/docs/resources.html +30 -0
  26. dvr_scan/docs/style.min.css +1 -0
  27. dvr_scan/dvr-scan-logo.png +0 -0
  28. dvr_scan/dvr-scan.ico +0 -0
  29. dvr_scan/dvr-scan.png +0 -0
  30. dvr_scan/opencv_loader.py +37 -0
  31. dvr_scan/overlays.py +251 -0
  32. dvr_scan/platform.py +236 -0
  33. dvr_scan/region.py +65 -0
  34. dvr_scan/scanner.py +1303 -0
  35. dvr_scan/shared/__init__.py +287 -0
  36. dvr_scan/shared/cli.py +291 -0
  37. dvr_scan/shared/settings.py +72 -0
  38. dvr_scan/subtractor.py +163 -0
  39. dvr_scan/video_joiner.py +188 -0
  40. dvr_scan_headless-1.8.dist-info/METADATA +119 -0
  41. dvr_scan_headless-1.8.dist-info/RECORD +45 -0
  42. dvr_scan_headless-1.8.dist-info/WHEEL +5 -0
  43. dvr_scan_headless-1.8.dist-info/entry_points.txt +2 -0
  44. dvr_scan_headless-1.8.dist-info/licenses/LICENSE +47 -0
  45. dvr_scan_headless-1.8.dist-info/top_level.txt +1 -0
dvr_scan/cli.py ADDED
@@ -0,0 +1,511 @@
1
+ #
2
+ # DVR-Scan: Video Motion Event Detection & Extraction Tool
3
+ # --------------------------------------------------------------
4
+ # [ Site: https://www.dvr-scan.com/ ]
5
+ # [ Repo: https://github.com/Breakthrough/DVR-Scan ]
6
+ #
7
+ # Copyright (C) 2016 Brandon Castellano <http://www.bcastell.com>.
8
+ # DVR-Scan is licensed under the BSD 2-Clause License; see the included
9
+ # LICENSE file, or visit one of the above pages for details.
10
+ #
11
+ import argparse
12
+ import typing as ty
13
+
14
+ from dvr_scan import get_license_info
15
+ from dvr_scan.config import CHOICE_MAP, USER_CONFIG_FILE_PATH, ConfigRegistry
16
+ from dvr_scan.platform import HAS_MOG2_CUDA
17
+ from dvr_scan.region import RegionValidator
18
+ from dvr_scan.shared import logfile_path
19
+ from dvr_scan.shared.cli import (
20
+ VERSION_STRING,
21
+ LicenseAction,
22
+ VersionAction,
23
+ float_type_check,
24
+ int_type_check,
25
+ kernel_size_type_check,
26
+ string_type_check,
27
+ timecode_type_check,
28
+ )
29
+
30
+ # In the CLI, -so/--scan-only is a different flag than -m/--output-mode, whereas in the
31
+ # config file they are the same option. Therefore, we remove the scan only choice
32
+ # from the -m/--output-mode choices listed in the CLI.
33
+ SCAN_ONLY_MODE = "scan_only"
34
+ assert SCAN_ONLY_MODE in CHOICE_MAP["output-mode"]
35
+
36
+ VALID_OUTPUT_MODES = [mode for mode in CHOICE_MAP["output-mode"] if mode != SCAN_ONLY_MODE]
37
+
38
+ BACKGROUND_SUBTRACTORS = ["MOG2", "CNT", "MOG2_CUDA"] if HAS_MOG2_CUDA else ["MOG2", "CNT"]
39
+
40
+
41
+ LOGFILE_PATH = logfile_path(name_prefix="dvr-scan")
42
+
43
+
44
+ class RegionAction(argparse.Action):
45
+ DEFAULT_ERROR_MESSAGE = "Region must be 3 or more points of the form X0 Y0 X1 Y1 X2 Y2 ..."
46
+
47
+ def __init__(
48
+ self,
49
+ option_strings,
50
+ dest,
51
+ nargs=None,
52
+ const=None,
53
+ default=None,
54
+ type=None,
55
+ choices=None,
56
+ required=False,
57
+ help=None,
58
+ metavar=None,
59
+ ):
60
+ assert nargs == "*"
61
+ assert const is None
62
+ super(RegionAction, self).__init__(
63
+ option_strings=option_strings,
64
+ dest=dest,
65
+ nargs=nargs,
66
+ const=const,
67
+ default=default,
68
+ type=type,
69
+ choices=choices,
70
+ required=required,
71
+ help=help,
72
+ metavar=metavar,
73
+ )
74
+
75
+ def __call__(self, parser, namespace, values: ty.List[str], option_string=None):
76
+ try:
77
+ region = RegionValidator(" ".join(values))
78
+ except ValueError as ex:
79
+ message = " ".join(str(arg) for arg in ex.args)
80
+ raise (
81
+ argparse.ArgumentError(
82
+ self, message if message else RegionAction.DEFAULT_ERROR_MESSAGE
83
+ )
84
+ ) from ex
85
+
86
+ # Append this ROI to any existing ones, if any.
87
+ items = getattr(namespace, self.dest, [])
88
+ items += [region.value]
89
+ setattr(namespace, self.dest, items)
90
+
91
+
92
+ def get_cli_parser(user_config: ConfigRegistry):
93
+ """Creates the DVR-Scan argparse command-line interface.
94
+
95
+ Arguments:
96
+ user_config: User configuration file registry.
97
+
98
+ Returns:
99
+ ArgumentParser object, which parse_args() can be called with.
100
+ """
101
+
102
+ parser = argparse.ArgumentParser(
103
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
104
+ argument_default=argparse.SUPPRESS,
105
+ exit_on_error=not user_config.get("debug"),
106
+ )
107
+
108
+ if hasattr(parser, "_optionals"):
109
+ parser._optionals.title = "arguments"
110
+
111
+ parser.add_argument(
112
+ "-V",
113
+ "--version",
114
+ action=VersionAction,
115
+ version=VERSION_STRING,
116
+ )
117
+
118
+ parser.add_argument(
119
+ "-L",
120
+ "--license",
121
+ action=LicenseAction,
122
+ version=get_license_info(),
123
+ )
124
+
125
+ parser.add_argument(
126
+ "-i",
127
+ "--input",
128
+ metavar="video_file",
129
+ required=True,
130
+ type=str,
131
+ nargs="+",
132
+ action="append",
133
+ help=(
134
+ "[REQUIRED] Path to input video. May specify multiple inputs with the same"
135
+ " resolution and framerate, or by specifying a wildcard/glob. Output"
136
+ " filenames are generated using the first video name only."
137
+ ),
138
+ )
139
+
140
+ parser.add_argument(
141
+ "-d",
142
+ "--output-dir",
143
+ metavar="path",
144
+ type=str,
145
+ help=(
146
+ "If specified, write output files in the given directory. If path does not"
147
+ " exist, it will be created. If unset, output files are written to the"
148
+ " current working directory."
149
+ ),
150
+ )
151
+
152
+ parser.add_argument(
153
+ "-o",
154
+ "--output",
155
+ metavar="video.avi",
156
+ type=str,
157
+ help=(
158
+ "If specified, all motion events will be written to a single file"
159
+ " in order (if not specified, separate files are created for each event)."
160
+ " Filename MUST end with .avi. Only supported in output mode OPENCV."
161
+ ),
162
+ )
163
+
164
+ parser.add_argument(
165
+ "-m",
166
+ "--output-mode",
167
+ metavar="mode",
168
+ type=string_type_check(VALID_OUTPUT_MODES, False, "mode"),
169
+ help=(
170
+ "Set mode for generating output files. Certain features may not work with "
171
+ " all output modes. Must be one of: %s.%s"
172
+ % (
173
+ ", ".join(VALID_OUTPUT_MODES),
174
+ user_config.get_help_string("output-mode"),
175
+ )
176
+ ),
177
+ )
178
+
179
+ parser.add_argument(
180
+ "-so",
181
+ "--scan-only",
182
+ action="store_true",
183
+ default=False,
184
+ help=(
185
+ "Only perform motion detection (does not write any files to disk)."
186
+ " If set, -m/--output-mode is ignored."
187
+ ),
188
+ )
189
+
190
+ parser.add_argument(
191
+ "-c",
192
+ "--config",
193
+ metavar="settings.cfg",
194
+ type=str,
195
+ help=(
196
+ "Path to config file. If not set, tries to load one from %s" % (USER_CONFIG_FILE_PATH)
197
+ ),
198
+ )
199
+
200
+ parser.add_argument(
201
+ "-r",
202
+ "--region-editor",
203
+ dest="region_editor",
204
+ action="store_true",
205
+ help=(
206
+ "Show region editor window. Motion detection will be limited to the enclosed area "
207
+ "during processing. Only single regions can be edited, but supports preview of "
208
+ "multiple regions if defined.%s" % user_config.get_help_string("region-editor")
209
+ ),
210
+ )
211
+
212
+ parser.add_argument(
213
+ "-a",
214
+ "--add-region",
215
+ metavar="X0 Y0 X1 Y1 X2 Y2",
216
+ dest="regions",
217
+ nargs="*",
218
+ action=RegionAction,
219
+ help=(
220
+ "Limit motion detection to a region of the frame. The region is defined as a sequence "
221
+ "of 3 or more points forming a closed shape inside the video. Coordinate 0 0 is top "
222
+ "left of the frame, and WIDTH-1 HEIGHT-1 is bottom right. Can be specified multiple "
223
+ "times to add more regions."
224
+ ),
225
+ )
226
+
227
+ parser.add_argument(
228
+ "-R",
229
+ "--load-region",
230
+ metavar="REGIONS.txt",
231
+ type=str,
232
+ help=(
233
+ "Load region data from file. Each line must be a list of points in the format "
234
+ "specified by -a/--add-region. Each line is treated as a separate polygon."
235
+ ),
236
+ )
237
+
238
+ parser.add_argument(
239
+ "-s",
240
+ "--save-region",
241
+ metavar="REGIONS.txt",
242
+ type=str,
243
+ help=(
244
+ "Save regions before processing. If REGIONS.txt exists it will be overwritten. "
245
+ "The region editor will save regions here instead of asking for a path."
246
+ ),
247
+ )
248
+
249
+ MOG2_CUDA = ", MOG2_CUDA (Nvidia GPU)" if HAS_MOG2_CUDA else ""
250
+ parser.add_argument(
251
+ "-b",
252
+ "--bg-subtractor",
253
+ metavar="type",
254
+ type=string_type_check(BACKGROUND_SUBTRACTORS, False, "type"),
255
+ help=(
256
+ "The type of background subtractor to use, must be one of: "
257
+ f" MOG2 (default), CNT (parallel){MOG2_CUDA}.%s"
258
+ )
259
+ % user_config.get_help_string("bg-subtractor"),
260
+ )
261
+
262
+ parser.add_argument(
263
+ "-t",
264
+ "--threshold",
265
+ metavar="value",
266
+ type=float_type_check(0.0, None, "value"),
267
+ help=(
268
+ "Threshold representing amount of motion in a frame required to trigger"
269
+ " motion events. Lower values are more sensitive to motion. If too high,"
270
+ " some movement in the scene may not be detected, while too low of a"
271
+ " threshold can result in false detections.%s"
272
+ % (user_config.get_help_string("threshold"))
273
+ ),
274
+ )
275
+
276
+ parser.add_argument(
277
+ "-k",
278
+ "--kernel-size",
279
+ metavar="size",
280
+ type=kernel_size_type_check(metavar="size"),
281
+ help=(
282
+ "Size in pixels of the noise reduction kernel. Must be odd number greater than 1, "
283
+ "0 to disable, or -1 to auto-set based on video resolution (default). If the kernel "
284
+ "size is set too large, some movement in the scene may not be detected.%s"
285
+ % (user_config.get_help_string("kernel-size"))
286
+ ),
287
+ )
288
+
289
+ parser.add_argument(
290
+ "-l",
291
+ "--min-event-length",
292
+ metavar="time",
293
+ type=timecode_type_check("time"),
294
+ help=(
295
+ "Length of time that must contain motion before triggering a new event. Can be"
296
+ " specified as frames (123), seconds (12.3s), or timecode (00:00:01).%s"
297
+ % user_config.get_help_string("min-event-length")
298
+ ),
299
+ )
300
+
301
+ parser.add_argument(
302
+ "-tb",
303
+ "--time-before-event",
304
+ metavar="time",
305
+ type=timecode_type_check("time"),
306
+ help=(
307
+ "Maximum amount of time to include before each event. Can be specified as"
308
+ " frames (123), seconds (12.3s), or timecode (00:00:01).%s"
309
+ % user_config.get_help_string("time-before-event")
310
+ ),
311
+ )
312
+
313
+ parser.add_argument(
314
+ "-tp",
315
+ "--time-post-event",
316
+ metavar="time",
317
+ type=timecode_type_check("time"),
318
+ help=(
319
+ "Maximum amount of time to include after each event. The event will end once no"
320
+ " motion has been detected for this period of time. Can be specified as frames (123),"
321
+ " seconds (12.3s), or timecode (00:00:01).%s"
322
+ % user_config.get_help_string("time-post-event")
323
+ ),
324
+ )
325
+
326
+ parser.add_argument(
327
+ "-st",
328
+ "--start-time",
329
+ metavar="time",
330
+ type=timecode_type_check("time"),
331
+ help=(
332
+ "Time to seek to in video before performing detection. Can be"
333
+ " given in number of frames (12345), seconds (number followed"
334
+ " by s, e.g. 123s or 123.45s), or timecode (HH:MM:SS[.nnn])."
335
+ ),
336
+ )
337
+
338
+ parser.add_argument(
339
+ "-dt",
340
+ "--duration",
341
+ metavar="time",
342
+ type=timecode_type_check("time"),
343
+ help=(
344
+ "Duration stop processing the input after (see -st for valid timecode formats)."
345
+ " Overrides -et."
346
+ ),
347
+ )
348
+
349
+ parser.add_argument(
350
+ "-et",
351
+ "--end-time",
352
+ metavar="time",
353
+ type=timecode_type_check("time"),
354
+ help=("Timecode to stop processing the input (see -st for valid timecode formats)."),
355
+ )
356
+
357
+ # TODO(1.9): Remove -roi (replaced by -r/--region-editor and -a/--add-region).
358
+ parser.add_argument(
359
+ "-roi",
360
+ "--region-of-interest",
361
+ dest="region_of_interest",
362
+ metavar="x0 y0 w h",
363
+ nargs="*",
364
+ help=argparse.SUPPRESS,
365
+ )
366
+
367
+ parser.add_argument(
368
+ "-bb",
369
+ "--bounding-box",
370
+ metavar="smooth_time",
371
+ type=timecode_type_check("smooth_time"),
372
+ nargs="?",
373
+ const=True,
374
+ help=(
375
+ "If set, draws a bounding box around the area where motion was detected. The amount"
376
+ " of temporal smoothing can be specified in either frames (12345) or seconds (number"
377
+ " followed by s, e.g. 123s or 123.45s). If omitted, defaults to 0.1s. If set to 0,"
378
+ " smoothing is disabled.%s"
379
+ % (user_config.get_help_string("bounding-box", show_default=False))
380
+ ),
381
+ )
382
+
383
+ parser.add_argument(
384
+ "-tc",
385
+ "--time-code",
386
+ action="store_true",
387
+ help=(
388
+ "Draw time code in top left corner of each frame.%s"
389
+ % user_config.get_help_string("time-code", show_default=False)
390
+ ),
391
+ )
392
+
393
+ parser.add_argument(
394
+ "-fm",
395
+ "--frame-metrics",
396
+ action="store_true",
397
+ help=(
398
+ "Draw frame metrics in top right corner of each frame.%s"
399
+ % user_config.get_help_string("frame-metrics", show_default=False)
400
+ ),
401
+ )
402
+
403
+ parser.add_argument(
404
+ "-mo",
405
+ "--mask-output",
406
+ metavar="motion_mask.avi",
407
+ type=str,
408
+ help=(
409
+ "Write a video containing the motion mask of each frame. Useful when tuning "
410
+ "detection parameters."
411
+ ),
412
+ )
413
+
414
+ parser.add_argument(
415
+ "-df",
416
+ "--downscale-factor",
417
+ metavar="factor",
418
+ type=int_type_check(0, None, "factor"),
419
+ help=(
420
+ "Integer factor to downscale (shrink) video before processing, to"
421
+ " improve performance. For example, if input video resolution"
422
+ " is 1024 x 400, and factor=2, each frame is reduced to"
423
+ " 1024/2 x 400/2=512 x 200 before processing.%s"
424
+ % (user_config.get_help_string("downscale-factor"))
425
+ ),
426
+ )
427
+
428
+ parser.add_argument(
429
+ "-fs",
430
+ "--frame-skip",
431
+ metavar="num_frames",
432
+ type=int_type_check(0, None, "num_frames"),
433
+ help=(
434
+ "Number of frames to skip after processing a given frame."
435
+ " Improves performance, at expense of frame and time accuracy,"
436
+ " and may increase probability of missing motion events."
437
+ " If set, -l, -tb, and -tp will all be scaled relative to the source"
438
+ " framerate. Values above 1 or 2 are not recommended.%s"
439
+ % (user_config.get_help_string("frame-skip"))
440
+ ),
441
+ )
442
+ parser.add_argument(
443
+ "-q",
444
+ "--quiet",
445
+ dest="quiet_mode",
446
+ action="store_true",
447
+ help=(
448
+ "Suppress all output except for final comma-separated list of motion events."
449
+ " Useful for computing or piping output directly into other programs/scripts.%s"
450
+ % user_config.get_help_string("quiet-mode")
451
+ ),
452
+ )
453
+
454
+ # Options that only take long-form.
455
+
456
+ parser.add_argument(
457
+ "--logfile",
458
+ metavar="file",
459
+ type=str,
460
+ help=(
461
+ "Appends application output to file. If file does not exist it will be created. "
462
+ f"Log path: {LOGFILE_PATH.parent}"
463
+ ),
464
+ )
465
+
466
+ parser.add_argument(
467
+ "--thumbnails",
468
+ metavar="method",
469
+ type=str,
470
+ default=None,
471
+ help=("Produce event thumbnail(s)."),
472
+ )
473
+
474
+ parser.add_argument(
475
+ "-v",
476
+ "--verbosity",
477
+ metavar="type",
478
+ type=string_type_check(CHOICE_MAP["verbosity"], False, "type"),
479
+ help=(
480
+ "Amount of verbosity to use for log output. Must be one of: %s.%s"
481
+ % (
482
+ ", ".join(CHOICE_MAP["verbosity"]),
483
+ user_config.get_help_string("verbosity"),
484
+ )
485
+ ),
486
+ )
487
+
488
+ parser.add_argument(
489
+ "--use-pts",
490
+ action="store_true",
491
+ default=False,
492
+ help=("Use OpenCV provided presentation timestamp instead of calculated version."),
493
+ )
494
+
495
+ parser.add_argument(
496
+ "--debug",
497
+ action="store_true",
498
+ help=argparse.SUPPRESS,
499
+ default=False,
500
+ )
501
+
502
+ # TODO: Support both input and output concatenation in ffmpeg mode. For concatenating events,
503
+ # we can still encode files for each event, and then join them with ffmpeg's codec copying
504
+ # mode as a final pass. Can also add a config param to keep the events.
505
+
506
+ # TODO: Add a mode that can dump frame scores (-s/--stats), and another mode
507
+ # that can dump the resulting frames after processing (-d/--dump-motion OUT.avi).
508
+ # Might also be helpful to overlay the frame score when using -d. Multiply the motion
509
+ # mask against the input image.
510
+
511
+ return parser