Skip to content

Commit 2d52935

Browse files
committed
[region-editor] Don't prompt for save path if one was provided
[scanner] Fix crash when `pillow` is not available [general] Cleanup some TODOs [app] Add link to Discord chat under About menu
1 parent ec274ad commit 2d52935

File tree

15 files changed

+227
-163
lines changed

15 files changed

+227
-163
lines changed

docs/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ The UI can be started by running `dvr-scan-app`, and is installed alongside the
2323
- [feature] Add ability to control video decoder via `input-mode` config option (`opencv`, `pyav`, `moviepy`)
2424
- Allows switching between `OpenCV` (default), `PyAV`, and `MoviePy` for video decoding
2525
- Certain backends provide substantial performance benefits, up to 50% in some cases (let us know which one works best!)
26+
- [bugfix] Fix crash on headless systems that don't have `pillow` installed
27+
- [general] The region editor no longer prompts for a save path if one was already specified via the `-s`/`--save-regions` option
28+
- [general] A small rolling log is now kept automatically to assist with debugging
29+
- Can be controlled with config file options `save-log`, `max-log-size` (default: 20 kB), `max-log-files` (default: 4)
30+
- Path can be found under help entry for `--logfile` by running `dvr-scan --help` or `dvr-scan-app --help`
2631
- [general] Minimum supported Python version is now 3.9
2732

2833
----------------------------------------------------------

dvr-scan.cfg

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,6 @@
1717
# used (it will be listed under the help text for the -c/--config option).
1818
#
1919

20-
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
21-
# GENERAL
22-
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
23-
24-
# Show region editor window (-r/--region-editor) before scanning.
25-
#region-editor = no
26-
27-
# Suppress all console output.
28-
#quiet-mode = no
29-
30-
# Verbosity of console output (debug, info, warning, error).
31-
# If set to debug, overrides quiet-mode unless set via command line.
32-
#verbosity = info
33-
34-
3520
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3621
# INPUT
3722
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -50,6 +35,9 @@
5035
# Number of frames to skip between processing when looking for motion events.
5136
#frame-skip = 0
5237

38+
# Always show the region editor window (-r/--region-editor) before scanning.
39+
#region-editor = no
40+
5341

5442
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
5543
# OUTPUT
@@ -170,3 +158,28 @@
170158

171159
# Text background color in the form (R,G,B) or 0xFFFFFF
172160
#text-bg-color = 0, 0, 0
161+
162+
163+
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
164+
# LOGGING
165+
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
166+
167+
# Suppress all console output. Output can still be saved using
168+
# the -l/--logfile option.
169+
#quiet-mode = no
170+
171+
# Verbosity of console output (debug, info, warning, error).
172+
# If set to debug, overrides quiet-mode unless set via command line.
173+
#verbosity = info
174+
175+
# Automatically save rolling logs. Useful for bug reports.
176+
# The path to logs for your system can be found under the help entry for
177+
# `--logfile` after running `dvr-scan --help` or `dvr-scan-app --help`.
178+
#save-log = yes
179+
180+
# Max size of a debug log in bytes.
181+
#max-log-size = 20000
182+
183+
# Max number of debug logs to keep. Old ones are deleted automatically.
184+
# Disk space usage will never exceed this times debug-log-max-len
185+
#max-log-files = 4

dvr_scan/__main__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ def main():
3232
if settings is None:
3333
sys.exit(EXIT_ERROR)
3434
logger = logging.getLogger("dvr_scan")
35+
# TODO(1.7): The logging redirect does not respect the original log level, which is now set to
36+
# DEBUG mode for rolling log files. https://github.com/tqdm/tqdm/issues/1272
37+
# We might have to just roll our own instead of relying on this one.
3538
redirect = FakeTqdmLoggingRedirect if settings.get("quiet-mode") else logging_redirect_tqdm
3639
# TODO: Use Python __debug__ mode instead of hard-coding as config option.
3740
debug_mode = settings.get("debug")
@@ -48,7 +51,6 @@ def main():
4851
if debug_mode:
4952
raise
5053
except KeyboardInterrupt:
51-
# TODO: This doesn't always work when the GUI is running.
5254
logger.info("Stopping (interrupt received)...", exc_info=show_traceback)
5355
if debug_mode:
5456
raise

dvr_scan/app/__main__.py

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,16 @@
1212
import argparse
1313
import logging
1414
import sys
15-
from logging.handlers import RotatingFileHandler
16-
from pathlib import Path
1715
from subprocess import CalledProcessError
1816

19-
from platformdirs import user_log_path
2017
from scenedetect import VideoOpenFailure
2118
from scenedetect.platform import FakeTqdmLoggingRedirect, logging_redirect_tqdm
2219

2320
import dvr_scan
2421
from dvr_scan import get_license_info
2522
from dvr_scan.app.application import Application
2623
from dvr_scan.config import CHOICE_MAP, USER_CONFIG_FILE_PATH, ConfigLoadFailure, ConfigRegistry
27-
from dvr_scan.platform import LOG_FORMAT_ROLLING_LOGS, attach_log_handler
28-
from dvr_scan.shared import ScanSettings, init_logging
24+
from dvr_scan.shared import ScanSettings, init_logging, logfile_path, setup_logger
2925
from dvr_scan.shared.cli import VERSION_STRING, LicenseAction, VersionAction, string_type_check
3026

3127
logger = logging.getLogger("dvr_scan")
@@ -34,9 +30,7 @@
3430
EXIT_SUCCESS: int = 0
3531
EXIT_ERROR: int = 1
3632

37-
# Keep the last 16 KiB of logfiles automatically
38-
MAX_LOGFILE_SIZE_KB = 16384
39-
MAX_LOGFILE_BACKUPS = 4
33+
LOGFILE_PATH = logfile_path(logfile_name="dvr-scan-app.log")
4034

4135

4236
def get_cli_parser():
@@ -71,7 +65,7 @@ def get_cli_parser():
7165
metavar="type",
7266
type=string_type_check(CHOICE_MAP["verbosity"], False, "type"),
7367
help=(
74-
"Amount of verbosity to use for log output. Must be one of: %s."
68+
"Verbosity type to use for log output. Must be one of: %s."
7569
% (", ".join(CHOICE_MAP["verbosity"]),)
7670
),
7771
)
@@ -81,8 +75,8 @@ def get_cli_parser():
8175
metavar="file",
8276
type=str,
8377
help=(
84-
"Path to log file for writing application output. If FILE already exists, the program"
85-
" output will be appended to the existing contents."
78+
"Appends application output to file. If file does not exist it will be created. "
79+
f"Debug log path: {LOGFILE_PATH}"
8680
),
8781
)
8882

@@ -109,45 +103,28 @@ def get_cli_parser():
109103
return parser
110104

111105

112-
def init_debug_logger() -> logging.Handler:
113-
"""Initialize rolling debug logger."""
114-
folder = user_log_path("DVR-Scan", False)
115-
folder.mkdir(parents=True, exist_ok=True)
116-
handler = RotatingFileHandler(
117-
folder / Path("dvr-scan.app.log"),
118-
maxBytes=MAX_LOGFILE_SIZE_KB * 1024,
119-
backupCount=MAX_LOGFILE_BACKUPS,
120-
)
121-
handler.setLevel(logging.DEBUG)
122-
handler.setFormatter(logging.Formatter(fmt=LOG_FORMAT_ROLLING_LOGS))
123-
return handler
124-
125-
126106
# TODO: There's a lot of duplicated code here between the CLI and GUI. See if we can combine some
127107
# of the handling of config file loading and exceptions to be consistent between the two.
128108
#
129109
# It would also be nice if both commands took the same set of arguments. Can probably re-use the
130110
# existing CLI parser.
131111
def main():
132112
"""Parse command line options and load config file settings."""
113+
# We defer printing the debug log until we know where to put it.
133114
init_log = []
134-
debug_log_handler = init_debug_logger()
135-
config_load_error = None
136-
failed_to_load_config = False
115+
failed_to_load_config = True
137116
config = ConfigRegistry()
117+
config_load_error = None
138118
# Create debug log handler and try to load the user config file.
139119
try:
140120
user_config = ConfigRegistry()
141121
user_config.load()
142122
config = user_config
143123
except ConfigLoadFailure as ex:
144124
config_load_error = ex
145-
146125
# Parse CLI args, override config if an override was specified on the command line.
147126
try:
148127
args = get_cli_parser().parse_args()
149-
# TODO: Add a UI element somewhere (e.g. in the about window) that indicates to the user
150-
# where the log files are being written.
151128
init_logging(args, config)
152129
init_log += [(logging.INFO, "DVR-Scan Application %s" % dvr_scan.__version__)]
153130
if config_load_error and not hasattr(args, "config"):
@@ -158,28 +135,38 @@ def main():
158135
init_logging(args, config_setting)
159136
config = config_setting
160137
init_log += config.consume_init_log()
138+
if config.config_dict:
139+
logger.debug("Loaded configuration:\n%s", str(config.config_dict))
140+
logger.debug("Program arguments:\n%s", str(args))
141+
settings = ScanSettings(args=args, config=config)
142+
if settings.get("save-log"):
143+
setup_logger(
144+
logfile_path=LOGFILE_PATH,
145+
max_size_bytes=settings.get("max-log-size"),
146+
max_files=settings.get("max-log-files"),
147+
)
148+
failed_to_load_config = False
161149
except ConfigLoadFailure as ex:
162150
init_log += ex.init_log
163151
if ex.reason is not None:
164152
init_log += [(logging.ERROR, "Error: %s" % str(ex.reason).replace("\t", " "))]
165153
failed_to_load_config = True
166154
config_load_error = ex
167155
finally:
168-
attach_log_handler(debug_log_handler)
169156
for log_level, log_str in init_log:
170157
logger.log(log_level, log_str)
171-
if failed_to_load_config:
172-
logger.critical("Failed to load config file.")
173-
logger.debug("Error loading config file:", exc_info=config_load_error)
174-
# Intentionally suppress the exception in release mode since we've already logged the
175-
# failure reason to the user above. We can now exit with an error code.
176-
raise SystemExit(1)
177-
178-
if config.config_dict:
179-
logger.debug("Loaded configuration:\n%s", str(config.config_dict))
180-
181-
logger.debug("Program arguments:\n%s", str(args))
182-
settings = ScanSettings(args=args, config=config)
158+
159+
if failed_to_load_config:
160+
logger.critical("Failed to load config file.")
161+
logger.debug("Error loading config file:", exc_info=config_load_error)
162+
# Intentionally suppress the exception in release mode since we've already logged the
163+
# failure reason to the user above. We can now exit with an error code.
164+
raise SystemExit(1)
165+
166+
# TODO(1.7): The logging redirect does not respect the original log level, which is now set to
167+
# DEBUG mode for rolling log files. https://github.com/tqdm/tqdm/issues/1272
168+
# We can just remove the use of a context manager here and install our own hooks into the
169+
# loggers instead as a follow-up action.
183170
redirect = FakeTqdmLoggingRedirect if settings.get("quiet-mode") else logging_redirect_tqdm
184171
show_traceback = getattr(logging, settings.get("verbosity").upper()) == logging.DEBUG
185172
# TODO: Use Python __debug__ mode instead of hard-coding as config option.

dvr_scan/app/application.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@
5454
MAX_THRESHOLD = 255.0
5555
MAX_DOWNSCALE_FACTOR = 128
5656
NO_REGIONS_SPECIFIED_TEXT = "No Region(s) Specified"
57-
58-
# TODO: Remove this and use the "debug" setting instead.
59-
SUPPRESS_EXCEPTIONS = False
6057
EXPAND_HORIZONTAL = tk.EW
6158

6259
logger = getLogger("dvr_scan")
@@ -1296,15 +1293,17 @@ def __init__(self, settings: ScanSettings, initial_videos: ty.List[str]):
12961293

12971294
self._create_menubar()
12981295

1299-
if not SUPPRESS_EXCEPTIONS:
1296+
# Make sure we don't suppress exceptions in debug mode.
1297+
# TODO: This should probably use the logger in release mode rather than the default from Tk.
1298+
if settings.get("debug"):
13001299

13011300
def error_handler(*args):
13021301
raise
13031302

13041303
self._root.report_callback_exception = error_handler
13051304

13061305
# Initialize UI state from config.
1307-
self._initialize_settings(settings)
1306+
self._initialize(settings)
13081307
for path in initial_videos:
13091308
self._input_area._add_video(path)
13101309

@@ -1366,6 +1365,11 @@ def _create_menubar(self):
13661365
command=lambda: webbrowser.open_new_tab("www.dvr-scan.com/guide"),
13671366
underline=0,
13681367
)
1368+
help_menu.add_command(
1369+
label="Join Discord Chat",
1370+
command=lambda: webbrowser.open_new_tab("https://discord.gg/69kf6f2Exb"),
1371+
underline=5,
1372+
)
13691373
# TODO: Add window to show log messages and copy them to clipboard or save to a logfile.
13701374
# help_menu.add_command(label="Debug Log", underline=0, state=tk.DISABLED)
13711375
help_menu.add_separator()
@@ -1475,9 +1479,9 @@ def _reset_config(self, program_default: bool = False):
14751479

14761480
def _reload_config(self, config: ConfigRegistry):
14771481
"""Reinitialize UI from another config."""
1478-
self._initialize_settings(ScanSettings(args=self._settings._args, config=config))
1482+
self._initialize(ScanSettings(args=self._settings._args, config=config))
14791483

1480-
def _initialize_settings(self, settings: ScanSettings):
1484+
def _initialize(self, settings: ScanSettings):
14811485
"""Initialize UI from both UI command-line arguments and config file."""
14821486
logger.debug("initializing UI state from settings")
14831487
# Store copy of settings internally.

0 commit comments

Comments
 (0)