DVR-Scan-Headless 1.8.2__tar.gz → 2.0__tar.gz

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 (101) hide show
  1. dvr_scan_headless-2.0/PKG-INFO +108 -0
  2. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/README.md +6 -6
  3. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr-scan.cfg +75 -24
  4. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/__init__.py +2 -2
  5. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/__main__.py +6 -0
  6. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/app/__main__.py +70 -3
  7. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/app/about_window.py +2 -1
  8. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/app/application.py +889 -159
  9. dvr_scan_headless-2.0/dvr_scan/app/common.py +143 -0
  10. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/app/region_editor.py +121 -24
  11. dvr_scan_headless-2.0/dvr_scan/app/scan_window.py +547 -0
  12. dvr_scan_headless-2.0/dvr_scan/app/startup.py +110 -0
  13. dvr_scan_headless-2.0/dvr_scan/app/thumbnails.py +298 -0
  14. dvr_scan_headless-2.0/dvr_scan/app/video_list.py +545 -0
  15. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/app/widgets.py +2 -0
  16. dvr_scan_headless-2.0/dvr_scan/app/wizard.py +1351 -0
  17. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/cli.py +65 -33
  18. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/config.py +287 -71
  19. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/controller.py +136 -35
  20. dvr_scan_headless-2.0/dvr_scan/docs/404.html +1 -0
  21. dvr_scan_headless-2.0/dvr_scan/docs/assets/javascripts/bundle.c21346df.min.js +15 -0
  22. dvr_scan_headless-2.0/dvr_scan/docs/assets/stylesheets/main.57a0d79d.min.css +6 -0
  23. dvr_scan_headless-2.0/dvr_scan/docs/assets/stylesheets/palette.fb2fffe2.min.css +4 -0
  24. dvr_scan_headless-2.0/dvr_scan/docs/changelog.html +1 -0
  25. dvr_scan_headless-2.0/dvr_scan/docs/docs.html +84 -0
  26. dvr_scan_headless-2.0/dvr_scan/docs/faq.html +15 -0
  27. dvr_scan_headless-2.0/dvr_scan/docs/guide.html +1 -0
  28. dvr_scan_headless-2.0/dvr_scan/docs/index.html +5 -0
  29. dvr_scan_headless-2.0/dvr_scan/docs/literature.html +7 -0
  30. dvr_scan_headless-2.0/dvr_scan/docs/resources.html +30 -0
  31. dvr_scan_headless-2.0/dvr_scan/docs/style.min.css +1 -0
  32. dvr_scan_headless-2.0/dvr_scan/dvr-scan-logo.png +0 -0
  33. dvr_scan_headless-2.0/dvr_scan/dvr-scan.ico +0 -0
  34. dvr_scan_headless-2.0/dvr_scan/dvr-scan.png +0 -0
  35. dvr_scan_headless-2.0/dvr_scan/encoder.py +756 -0
  36. dvr_scan_headless-2.0/dvr_scan/presets.py +277 -0
  37. dvr_scan_headless-2.0/dvr_scan/report.py +120 -0
  38. dvr_scan_headless-2.0/dvr_scan/scanner.py +1328 -0
  39. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/shared/__init__.py +28 -4
  40. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/shared/cli.py +7 -2
  41. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/shared/settings.py +5 -0
  42. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/subtractor.py +31 -0
  43. dvr_scan_headless-2.0/dvr_scan/video_input.py +295 -0
  44. dvr_scan_headless-2.0/dvr_scan_headless.egg-info/PKG-INFO +108 -0
  45. {dvr_scan_headless-1.8.2/DVR_Scan_Headless.egg-info → dvr_scan_headless-2.0/dvr_scan_headless.egg-info}/SOURCES.txt +30 -13
  46. dvr_scan_headless-2.0/dvr_scan_headless.egg-info/requires.txt +11 -0
  47. dvr_scan_headless-2.0/pyproject.toml +123 -0
  48. dvr_scan_headless-2.0/setup.cfg +4 -0
  49. dvr_scan_headless-2.0/tests/test_application.py +1024 -0
  50. dvr_scan_headless-2.0/tests/test_cli.py +677 -0
  51. dvr_scan_headless-2.0/tests/test_encode_mode.py +267 -0
  52. dvr_scan_headless-2.0/tests/test_encoder.py +546 -0
  53. dvr_scan_headless-2.0/tests/test_presets.py +373 -0
  54. dvr_scan_headless-2.0/tests/test_release_scripts.py +124 -0
  55. dvr_scan_headless-2.0/tests/test_report.py +85 -0
  56. dvr_scan_headless-2.0/tests/test_scan_context.py +812 -0
  57. dvr_scan_headless-2.0/tests/test_startup.py +306 -0
  58. dvr_scan_headless-2.0/tests/test_subtractor.py +51 -0
  59. dvr_scan_headless-2.0/tests/test_thumbnails.py +262 -0
  60. dvr_scan_headless-2.0/tests/test_video_input.py +191 -0
  61. dvr_scan_headless-2.0/tests/test_video_list.py +599 -0
  62. dvr_scan_headless-2.0/tests/test_wizard.py +833 -0
  63. dvr_scan_headless-1.8.2/DVR_Scan_Headless.egg-info/PKG-INFO +0 -117
  64. dvr_scan_headless-1.8.2/DVR_Scan_Headless.egg-info/requires.txt +0 -7
  65. dvr_scan_headless-1.8.2/PKG-INFO +0 -117
  66. dvr_scan_headless-1.8.2/dvr_scan/app/common.py +0 -31
  67. dvr_scan_headless-1.8.2/dvr_scan/app/scan_window.py +0 -258
  68. dvr_scan_headless-1.8.2/dvr_scan/docs/404.html +0 -1
  69. dvr_scan_headless-1.8.2/dvr_scan/docs/assets/javascripts/bundle.88dd0f4e.min.js +0 -15
  70. dvr_scan_headless-1.8.2/dvr_scan/docs/assets/stylesheets/main.6f8fc17f.min.css +0 -1
  71. dvr_scan_headless-1.8.2/dvr_scan/docs/assets/stylesheets/palette.06af60db.min.css +0 -1
  72. dvr_scan_headless-1.8.2/dvr_scan/docs/changelog.html +0 -1
  73. dvr_scan_headless-1.8.2/dvr_scan/docs/docs.html +0 -73
  74. dvr_scan_headless-1.8.2/dvr_scan/docs/faq.html +0 -13
  75. dvr_scan_headless-1.8.2/dvr_scan/docs/guide.html +0 -1
  76. dvr_scan_headless-1.8.2/dvr_scan/docs/index.html +0 -5
  77. dvr_scan_headless-1.8.2/dvr_scan/docs/resources.html +0 -30
  78. dvr_scan_headless-1.8.2/dvr_scan/docs/style.min.css +0 -1
  79. dvr_scan_headless-1.8.2/dvr_scan/dvr-scan-logo.png +0 -0
  80. dvr_scan_headless-1.8.2/dvr_scan/dvr-scan.ico +0 -0
  81. dvr_scan_headless-1.8.2/dvr_scan/dvr-scan.png +0 -0
  82. dvr_scan_headless-1.8.2/dvr_scan/scanner.py +0 -1310
  83. dvr_scan_headless-1.8.2/dvr_scan/video_joiner.py +0 -192
  84. dvr_scan_headless-1.8.2/pyproject.toml +0 -45
  85. dvr_scan_headless-1.8.2/setup.cfg +0 -70
  86. dvr_scan_headless-1.8.2/tests/test_cli.py +0 -307
  87. dvr_scan_headless-1.8.2/tests/test_maxarea.py +0 -111
  88. dvr_scan_headless-1.8.2/tests/test_scan_context.py +0 -251
  89. dvr_scan_headless-1.8.2/tests/test_video_joiner.py +0 -37
  90. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/LICENSE +0 -0
  91. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dist/package-info.rst +0 -0
  92. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/LICENSE +0 -0
  93. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/app/__init__.py +0 -0
  94. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/detector.py +0 -0
  95. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/opencv_loader.py +0 -0
  96. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/overlays.py +0 -0
  97. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/platform.py +0 -0
  98. {dvr_scan_headless-1.8.2 → dvr_scan_headless-2.0}/dvr_scan/region.py +0 -0
  99. {dvr_scan_headless-1.8.2/DVR_Scan_Headless.egg-info → dvr_scan_headless-2.0/dvr_scan_headless.egg-info}/dependency_links.txt +0 -0
  100. {dvr_scan_headless-1.8.2/DVR_Scan_Headless.egg-info → dvr_scan_headless-2.0/dvr_scan_headless.egg-info}/entry_points.txt +0 -0
  101. {dvr_scan_headless-1.8.2/DVR_Scan_Headless.egg-info → dvr_scan_headless-2.0/dvr_scan_headless.egg-info}/top_level.txt +0 -0
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: dvr-scan-headless
3
+ Version: 2.0
4
+ Summary: Tool for finding and extracting motion events in video files (e.g. security camera footage).
5
+ Author-email: Brandon Castellano <brandon248@gmail.com>
6
+ License: BSD-2-Clause
7
+ Project-URL: Bug Tracker, https://github.com/Breakthrough/DVR-Scan/issues
8
+ Project-URL: Documentation, https://www.dvr-scan.com/docs
9
+ Project-URL: Homepage, https://www.dvr-scan.com/
10
+ Project-URL: Repository, https://github.com/Breakthrough/DVR-Scan
11
+ Keywords: analysis,computer-vision,video
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Console :: Curses
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: BSD License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Multimedia :: Video
25
+ Classifier: Topic :: Multimedia :: Video :: Conversion
26
+ Classifier: Topic :: Multimedia :: Video :: Non-Linear Editor
27
+ Classifier: Topic :: Utilities
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: numpy
32
+ Requires-Dist: platformdirs
33
+ Requires-Dist: scenedetect-headless[pyav]~=0.7.2
34
+ Requires-Dist: av
35
+ Requires-Dist: tqdm
36
+ Requires-Dist: pillow
37
+ Requires-Dist: opencv-contrib-python-headless
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest; extra == "dev"
40
+ Requires-Dist: ruff; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ ![DVR-Scan](https://www.dvr-scan.com/assets/dvr-scan-logo.png)
44
+
45
+ :vhs: Find and extract motion events in videos.
46
+
47
+ ------------------------------------------------
48
+
49
+ ### Latest Release: v2.0 (November 8, 2026)
50
+
51
+ **Website**: [dvr-scan.com](https://www.dvr-scan.com)
52
+
53
+ **User Guide**: [dvr-scan.com/guide](https://www.dvr-scan.com/guide/)
54
+
55
+ **Documentation**: [dvr-scan.com/docs](https://www.dvr-scan.com/docs/)
56
+
57
+ **Discord**: [discord.gg/69kf6f2Exb](https://discord.gg/69kf6f2Exb)
58
+
59
+ ------------------------------------------------------
60
+
61
+ DVR-Scan is a command-line application that **automatically detects motion events in video files** (e.g. security camera footage). DVR-Scan looks for areas in footage containing motion, and saves each event to a separate video clip. DVR-Scan is free and open-source software, and works on Windows, Linux, and Mac.
62
+
63
+ ## Quick Install
64
+
65
+ pip install dvr-scan --upgrade
66
+
67
+ Windows builds (installer + portable) are also available on [the Downloads page](https://www.dvr-scan.com/download/).
68
+
69
+ ## Quickstart (UI)
70
+
71
+ Start DVR-Scan (run `dvr-scan-app` or click the app shortcut). The Scan Wizard walks you through each step: add your input videos, pick a preset, choose where to save the output, and hit **Start Scan**:
72
+
73
+ <img alt="scan wizard" src="https://www.dvr-scan.com/assets/app-wizard-videos.jpg" width="480"/>
74
+
75
+ See the [User Guide](https://www.dvr-scan.com/guide/) for a more comprehensive overview.
76
+
77
+ ## Quickstart (CLI)
78
+
79
+ Scan `video.mp4` (separate clips for each event):
80
+
81
+ dvr-scan -i video.mp4
82
+
83
+ Select a region to scan using [the region editor](https://www.dvr-scan.com/guide/):
84
+
85
+ dvr-scan -i video.mp4 -r
86
+
87
+ <img alt="example of region editor" src="https://www.dvr-scan.com/assets/region-editor-mask.jpg" width="480"/>
88
+
89
+ Select a region to scan using command line (list of points as X Y):
90
+
91
+ dvr-scan -i video.mp4 -a 50 50 100 50 100 100 100 50
92
+
93
+ Draw boxes around motion:
94
+
95
+ dvr-scan -i video.mp4 -bb
96
+
97
+ <img alt="example of bounding boxes" src="https://www.dvr-scan.com/assets/bounding-box.gif" width="480"/>
98
+
99
+ Use `ffmpeg` to extract events:
100
+
101
+ dvr-scan -i video.mp4 -m ffmpeg
102
+
103
+ See [the documentation](https://www.dvr-scan.com/docs) for a complete list of all command-line and configuration file options which can be set. You can also type `dvr-scan --help` for an overview of command line options. Some program options can also be set [using a config file](https://www.dvr-scan.com/docs/#config-file).
104
+
105
+ ------------------------------------------------
106
+
107
+ Copyright © 2016-2026 Brandon Castellano. All rights reserved.
108
+ Licensed under BSD 2-Clause (see the LICENSE file for details).
@@ -1,10 +1,10 @@
1
- ![DVR-Scan Logo](https://raw.githubusercontent.com/Breakthrough/DVR-Scan/main/docs/assets/dvr-scan-logo.png)
1
+ ![DVR-Scan](https://www.dvr-scan.com/assets/dvr-scan-logo.png)
2
2
 
3
3
  :vhs: Find and extract motion events in videos.
4
4
 
5
5
  ------------------------------------------------
6
6
 
7
- ### Latest Release: v1.8.2 (January 18, 2026)
7
+ ### Latest Release: v2.0 (November 8, 2026)
8
8
 
9
9
  **Website**: [dvr-scan.com](https://www.dvr-scan.com)
10
10
 
@@ -26,9 +26,9 @@ Windows builds (installer + portable) are also available on [the Downloads page]
26
26
 
27
27
  ## Quickstart (UI)
28
28
 
29
- Start DVR-Scan (run `dvr-scan-app` or click the app shortcut), **Add** your input videos, and hit **Start**:
29
+ Start DVR-Scan (run `dvr-scan-app` or click the app shortcut). The Scan Wizard walks you through each step: add your input videos, pick a preset, choose where to save the output, and hit **Start Scan**:
30
30
 
31
- <img alt="main app window" src="https://raw.githubusercontent.com/Breakthrough/DVR-Scan/releases/1.8/docs/assets/app-main-window.jpg" width="480"/>
31
+ <img alt="scan wizard" src="https://www.dvr-scan.com/assets/app-wizard-videos.jpg" width="480"/>
32
32
 
33
33
  See the [User Guide](https://www.dvr-scan.com/guide/) for a more comprehensive overview.
34
34
 
@@ -42,7 +42,7 @@ Select a region to scan using [the region editor](https://www.dvr-scan.com/guide
42
42
 
43
43
  dvr-scan -i video.mp4 -r
44
44
 
45
- <img alt="example of region editor" src="https://raw.githubusercontent.com/Breakthrough/DVR-Scan/releases/1.8/docs/assets/region-editor-mask.jpg" width="480"/>
45
+ <img alt="example of region editor" src="https://www.dvr-scan.com/assets/region-editor-mask.jpg" width="480"/>
46
46
 
47
47
  Select a region to scan using command line (list of points as X Y):
48
48
 
@@ -52,7 +52,7 @@ Draw boxes around motion:
52
52
 
53
53
  dvr-scan -i video.mp4 -bb
54
54
 
55
- <img alt="example of bounding boxes" src="https://raw.githubusercontent.com/Breakthrough/DVR-Scan/releases/1.8/docs/assets/bounding-box.gif" width="480"/>
55
+ <img alt="example of bounding boxes" src="https://www.dvr-scan.com/assets/bounding-box.gif" width="480"/>
56
56
 
57
57
  Use `ffmpeg` to extract events:
58
58
 
@@ -9,25 +9,34 @@
9
9
  # Windows: C:/Users/%USERNAME%/AppData/Local/DVR-Scan/dvr-scan.cfg
10
10
  #
11
11
  # Linux: ~/.config/DVR-Scan/dvr-scan.cfg
12
- # $XDG_CONFIG_HOME/dvr-scan.cfg
12
+ # ($XDG_CONFIG_HOME/DVR-Scan/dvr-scan.cfg if XDG_CONFIG_HOME is set)
13
13
  #
14
- # Mac: ~/Library/Preferences/DVR-Scan/dvr-scan.cfg
14
+ # macOS: ~/Library/Application Support/DVR-Scan/dvr-scan.cfg
15
15
  #
16
16
  # Run `dvr-scan --help` to see the exact path on your system which will be
17
17
  # used (it will be listed under the help text for the -c/--config option).
18
18
  #
19
19
 
20
+ # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
21
+ # APPLICATION
22
+ # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
23
+
24
+ # Which mode the DVR-Scan app (dvr-scan-app) starts in: WIZARD or ADVANCED.
25
+ # The Scan Wizard provides a simplified step-by-step workflow, while Advanced
26
+ # Mode shows all settings at once. If this option is not set, the app starts
27
+ # in ADVANCED mode. Can also be changed from File -> Startup Mode in the app,
28
+ # or overridden for one launch with `dvr-scan-app --startup-mode`. Has no
29
+ # effect on the command line.
30
+ startup-mode = WIZARD
31
+
32
+
20
33
  # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
21
34
  # INPUT
22
35
  # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
23
36
 
24
- # Which mode to use for decoding frames. Possible values are: OPENCV, PYAV,
37
+ # Which mode to use for decoding frames. Possible values are: PYAV, OPENCV,
25
38
  # MOVIEPY.
26
- #input-mode = OPENCV
27
-
28
- # Use presentation time instead of frame number for timestamps. May improve
29
- # timestamp accuracy with videos and network streams that may skip frames.
30
- #use-pts = no
39
+ #input-mode = PYAV
31
40
 
32
41
  # Integer factor to shrink video before processing. Values <= 1 have no effect.
33
42
  #downscale-factor = 0
@@ -38,6 +47,10 @@
38
47
  # Always show the region editor window (-r/--region-editor) before scanning.
39
48
  #region-editor = no
40
49
 
50
+ # Load region data from a file. Each line must be a list of points in the
51
+ # format specified by -a/--add-region. Each line is treated as a polygon.
52
+ #load-region = regions.txt
53
+
41
54
 
42
55
  # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
43
56
  # OUTPUT
@@ -51,38 +64,75 @@
51
64
  #open-output-dir = yes
52
65
 
53
66
  # Method of generating output videos. Possible values are: SCAN_ONLY, OPENCV,
54
- # FFMPEG, COPY. Not all features are supported in FFMPEG/COPY mode.
55
- #output-mode = OPENCV
67
+ # FFMPEG, COPY, ENCODE. The default (ENCODE) produces MP4 (H.264) output with
68
+ # overlay and audio support, and requires ffmpeg (OPENCV is used as a fallback
69
+ # if ffmpeg is missing). Not all features are supported in FFMPEG/COPY mode.
70
+ #output-mode = ENCODE
71
+
72
+ # Encoder parameters used when generating output files in ENCODE mode. Stream
73
+ # mappings are generated automatically and must not be set here. If no -pix_fmt
74
+ # is given, -pix_fmt yuv420p is added and odd frame sizes are padded to even.
75
+ #encode-args = -c:v libx264 -preset veryfast -crf 22 -c:a aac
56
76
 
57
77
  # Arguments to add before the input when calling ffmpeg in output-mode FFMPEG
58
78
  # or COPY. Note that `-y` and `-nostdin` are always added.
59
79
  #ffmpeg-input-args = -v error
60
80
 
61
81
  # Encoder parameters used when generating output files in FFMPEG mode.
62
- #ffmpeg-output-args = -map 0 -c:v libx264 -preset fast -crf 21 -c:a aac -sn
82
+ #ffmpeg-output-args = -map 0:v:0 -map 0:a? -map 0:s? -c:v libx264 -preset veryfast -crf 22 -c:a aac
63
83
 
64
84
  # Four-letter identifier of the encoder/video codec to use in OPENCV mode.
65
85
  # Possible values are: XVID, MP4V, MP42, H264
66
86
  #opencv-codec = XVID
67
87
 
88
+ # Only perform motion detection without writing any files to disk. If set,
89
+ # output-mode is ignored.
90
+ #scan-only = no
91
+
92
+ # Produce thumbnail image(s) for each event. Possible values are: HIGHSCORE
93
+ # (saves the frame with the highest motion score).
94
+ #thumbnails = HIGHSCORE
95
+
96
+ # Format of event list printed at the end of a scan (this is exactly what is
97
+ # output in quiet mode). Must be one of:
98
+ # parts: start-end,start-end,... (syntax of `mkvmerge --split parts:`)
99
+ # timecodes: start,end,start,end,... (syntax of `mkvmerge --split timecodes:`)
100
+ # lines: one start-end pair per line (accepted as file input)
101
+ #timecode-list-format = timecodes
102
+
68
103
 
69
104
  # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
70
105
  # MOTION
71
106
  # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
72
107
 
108
+ # Maximum number of motion events to detect before stopping the scan early
109
+ # (0 for no limit).
110
+ #max-events = 0
111
+
73
112
  # Amount of time which must have motion in each frame to trigger an event.
74
- #min-event-length = 2
113
+ #min-event-length = 0.1s
114
+
115
+ # Maximum length of a single event, including pre/post-event padding. Longer
116
+ # events are split into consecutive events of at most this length (no padding
117
+ # is added at a split; if motion continues the next event starts there).
118
+ #max-event-length = 999999s
75
119
 
76
120
  # Amount of time to include before an event.
77
121
  #time-before-event = 1.5s
78
122
 
79
- # Amount of time to include after an event.
123
+ # Amount of time to include after an event. Cannot be larger than merge-window.
80
124
  #time-post-event = 2.0s
81
125
 
82
- # Threshold representing amount of motion in a frame (or the ROI, if set) for
83
- # a motion event to be triggered. Lower values require less movement, and are
84
- # more sensitive to motion. If too high, some movement may not be detected,
85
- # while too low of a threshold can result in false detection events.
126
+ # Merge groups of motion which occur less than this amount of time apart into
127
+ # the same event. The default (auto) uses the same value as time-post-event,
128
+ # which matches the behavior of previous versions.
129
+ #merge-window = auto
130
+
131
+ # Threshold representing amount of motion in a frame (or in the detection
132
+ # regions, if set) for a motion event to be triggered. Lower values require
133
+ # less movement, and are more sensitive to motion. If too high, some movement
134
+ # may not be detected, while too low of a threshold can result in false
135
+ # detection events.
86
136
  #threshold = 0.15
87
137
 
88
138
 
@@ -159,14 +209,14 @@
159
209
  # Enable/disable frame metrics overlay in top right of video frame.
160
210
  #frame-metrics = yes
161
211
 
162
- # Size of border around text, in pixels. Must be <= text-margin.
212
+ # Padding between the text and the edge of its background box, in pixels.
163
213
  #text-border = 4
164
214
 
165
- # Size of margin around border, in pixels.
215
+ # Margin between the background box and the edge of the frame, in pixels.
166
216
  #text-margin = 4
167
217
 
168
218
  # Font scale factor.
169
- #text-font-scale = 2.0
219
+ #text-font-scale = 1.0
170
220
 
171
221
  # Thickness of font (integer values only).
172
222
  #text-font-thickness = 2
@@ -182,12 +232,13 @@
182
232
  # LOGGING
183
233
  # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
184
234
 
185
- # Suppress all console output. Output can still be saved using
186
- # the -l/--logfile option.
235
+ # Suppress all console output except for the final event list (see
236
+ # timecode-list-format). Output can still be saved using the --logfile option.
187
237
  #quiet-mode = no
188
238
 
189
- # Verbosity of console output (debug, info, warning, error).
190
- # If set to debug, overrides quiet-mode unless set via command line.
239
+ # Verbosity of log output (debug, info, warning, error). Applies to the console
240
+ # and to --logfile. If quiet-mode is enabled, nothing except the final event
241
+ # list is printed to the console regardless of verbosity.
191
242
  #verbosity = info
192
243
 
193
244
  # Automatically save rolling logs. Useful for bug reports.
@@ -24,7 +24,7 @@ main modules under `dvr_scan` are organized as follows:
24
24
 
25
25
  There are also a few helper modules:
26
26
 
27
- ``video_joiner``: concatenates multiple input videos
27
+ ``video_input``: opens one or more input videos as a single contiguous stream
28
28
 
29
29
  ``opencv_loader``: helper for resolving dynamic libraries used by OpenCV
30
30
 
@@ -35,7 +35,7 @@ import pkgutil
35
35
  import sys
36
36
 
37
37
  # Used for module/distribution identification.
38
- __version__ = "1.8.2"
38
+ __version__ = "2.0"
39
39
 
40
40
  # Handle loading OpenCV. This **MUST** be first before any other DVR-Scan or third-party
41
41
  # packages are imported which might attempt to import the `cv2` module.
@@ -65,6 +65,12 @@ def main():
65
65
  logger.critical("Error (Not Implemented): %s", str(ex), exc_info=show_traceback)
66
66
  if debug_mode:
67
67
  raise
68
+ except (OSError, RuntimeError) as ex:
69
+ # I/O errors (e.g. an unwritable output path) and ffmpeg failures: the message
70
+ # is enough to act on, so only show the traceback in debug mode.
71
+ logger.critical("Error: %s", str(ex), exc_info=show_traceback)
72
+ if debug_mode:
73
+ raise
68
74
  except Exception as ex:
69
75
  logger.critical("Critical Error: %s", str(ex), exc_info=True)
70
76
  if debug_mode:
@@ -21,6 +21,13 @@ from scenedetect import VideoOpenFailure
21
21
  import dvr_scan
22
22
  from dvr_scan import get_license_info
23
23
  from dvr_scan.app.application import Application
24
+ from dvr_scan.app.startup import (
25
+ STARTUP_MODE_OPTION,
26
+ has_prior_logs,
27
+ normalize_startup_mode,
28
+ resolve_default_startup_mode,
29
+ save_startup_mode,
30
+ )
24
31
  from dvr_scan.config import CHOICE_MAP, USER_CONFIG_FILE_PATH, ConfigLoadFailure, ConfigRegistry
25
32
  from dvr_scan.shared import (
26
33
  ScanSettings,
@@ -40,6 +47,13 @@ EXIT_ERROR: int = 1
40
47
  LOGFILE_PATH = logfile_path(name_prefix="dvr-scan-app")
41
48
 
42
49
 
50
+ def _startup_mode_type(value: str) -> str:
51
+ """argparse type for `--startup-mode`: accepts the legacy alias "classic" (normalized
52
+ to "advanced") in addition to the values in CHOICE_MAP, any case."""
53
+ check = string_type_check(CHOICE_MAP[STARTUP_MODE_OPTION], False, "mode")
54
+ return check(normalize_startup_mode(value))
55
+
56
+
43
57
  def get_cli_parser():
44
58
  parser = argparse.ArgumentParser(
45
59
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
@@ -101,6 +115,16 @@ def get_cli_parser():
101
115
  help=("Ignore loading the user config file if present."),
102
116
  )
103
117
 
118
+ parser.add_argument(
119
+ "--startup-mode",
120
+ metavar="mode",
121
+ type=_startup_mode_type,
122
+ help=(
123
+ "Mode to start the application in. Must be one of: %s. Overrides the startup-mode "
124
+ "config option for this launch only." % (", ".join(CHOICE_MAP["startup-mode"]),)
125
+ ),
126
+ )
127
+
104
128
  parser.add_argument(
105
129
  "--debug",
106
130
  action="store_true",
@@ -178,20 +202,63 @@ def main():
178
202
  logger.debug("Error loading config file:", exc_info=config_load_error)
179
203
  # Intentionally suppress the exception in release mode since we've already logged the
180
204
  # failure reason to the user above. We can now exit with an error code.
181
- errors = [message for (severity, message) in init_log if severity >= logging.ERROR]
205
+ errors = "\n".join(message for (severity, message) in init_log if severity >= logging.ERROR)
182
206
  tkinter.messagebox.showerror(
183
207
  title="DVR-Scan Startup Error",
184
- message=f"Error loading config file. Reason:\n\n{'\n'.join(errors)}",
208
+ message=f"Error loading config file. Reason:\n\n{errors}",
185
209
  )
186
210
  raise SystemExit(1)
187
211
 
188
212
  show_traceback = getattr(logging, settings.get("verbosity").upper()) == logging.DEBUG
189
213
  # TODO: Use Python __debug__ mode instead of hard-coding as config option.
190
214
  debug_mode = settings.get("debug")
215
+
216
+ # Resolve the default startup mode (wizard or advanced). The first-run heuristic only
217
+ # applies when the user config is in play and no file was given explicitly, since it
218
+ # may write the result to the user config file.
219
+ allow_heuristic = not args.ignore_user_config and not hasattr(args, "config")
220
+ default_startup_mode, persist_startup_mode = resolve_default_startup_mode(
221
+ config,
222
+ user_config_exists=USER_CONFIG_FILE_PATH.exists(),
223
+ prior_logs=has_prior_logs(LOGFILE_PATH.parent, exclude=LOGFILE_PATH),
224
+ allow_heuristic=allow_heuristic,
225
+ )
226
+ if persist_startup_mode:
227
+ try:
228
+ path = save_startup_mode(default_startup_mode)
229
+ logger.debug("first run: saved startup-mode = %s to %s", default_startup_mode, path)
230
+ except (OSError, UnicodeError) as ex:
231
+ logger.warning("Failed to save startup mode to user config file: %s", ex)
232
+ startup_mode = (
233
+ normalize_startup_mode(args.startup_mode)
234
+ if hasattr(args, "startup_mode")
235
+ else default_startup_mode
236
+ )
237
+ # The File > Startup Mode menu shows and edits the value in the user config file. With
238
+ # -c or --ignore-user-config that is not the config we launched from, so read it here
239
+ # (falling back to the program default if the user config is missing or invalid).
240
+ menu_startup_mode = default_startup_mode
241
+ if not allow_heuristic:
242
+ try:
243
+ user_config = ConfigRegistry()
244
+ user_config.load(log_level=logging.DEBUG)
245
+ menu_startup_mode = normalize_startup_mode(user_config.get(STARTUP_MODE_OPTION))
246
+ except ConfigLoadFailure:
247
+ menu_startup_mode = normalize_startup_mode(ConfigRegistry().get(STARTUP_MODE_OPTION))
248
+ logger.debug(
249
+ "startup mode: %s (default: %s, user config: %s)",
250
+ startup_mode,
251
+ default_startup_mode,
252
+ menu_startup_mode,
253
+ )
254
+
191
255
  with logging_redirect_tqdm(loggers=[logger]):
192
256
  try:
193
257
  app = Application(
194
- settings=settings, initial_videos=args.input if hasattr(args, "input") else []
258
+ settings=settings,
259
+ initial_videos=args.input if hasattr(args, "input") else [],
260
+ startup_mode=startup_mode,
261
+ default_startup_mode=menu_startup_mode,
195
262
  )
196
263
  app.run()
197
264
 
@@ -27,7 +27,8 @@ from dvr_scan.platform import get_system_version_info
27
27
 
28
28
  TITLE = "About DVR-Scan"
29
29
  COPYRIGHT = (
30
- f"DVR-Scan {dvr_scan.__version__}\n\nCopyright © Brandon Castellano.\nAll rights reserved."
30
+ f"DVR-Scan {dvr_scan.__version__}\n\n"
31
+ "Copyright \N{COPYRIGHT SIGN} Brandon Castellano.\nAll rights reserved."
31
32
  )
32
33
 
33
34