From c64de2f93c764269abf4dc85bd998eedeee2d702 Mon Sep 17 00:00:00 2001 From: Breakthrough Date: Sun, 2 Nov 2025 20:24:23 -0500 Subject: [PATCH 1/2] [build] Drop Python 3.9 support (EOL) --- .github/workflows/build.yml | 2 +- docs/changelog.md | 1 + docs/download.md | 2 +- setup.cfg | 3 +-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07748a2..7f83fa5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [macos-14, macos-latest, ubuntu-22.04, ubuntu-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/docs/changelog.md b/docs/changelog.md index 1a16618..f1aa51d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -391,4 +391,5 @@ This version of DVR-Scan includes a new, faster background subtraction algorithm ### 1.9 + - [general] Minimum Python version is now 3.10 ([EOL](https://devguide.python.org/versions/)) * [bugfix] Fix `quiet-mode` setting (`-q`/`--quiet` flag) still allowing extraneous output diff --git a/docs/download.md b/docs/download.md index 46c844f..223343f 100644 --- a/docs/download.md +++ b/docs/download.md @@ -21,7 +21,7 @@ hide: python3 -m pip install dvr-scan -DVR-Scan requires Python 3.9 or higher to run, and works on Windows, Linux, and OSX. [`pipx` is recommended](https://pipx.pypa.io/stable/installation/) for installing DVR-Scan, however installing via `pip` or from source is also supported. +DVR-Scan requires Python 3.10 or higher to run, and works on Windows, Linux, and OSX. [`pipx` is recommended](https://pipx.pypa.io/stable/installation/) for installing DVR-Scan, however installing via `pip` or from source is also supported. Linux users may need to install the `python3-tk` package (e.g. `sudo apt install python3-tk`) to run the region editor. diff --git a/setup.cfg b/setup.cfg index bc8624c..6c89a86 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,7 +32,6 @@ classifiers = License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python :: 3 - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 @@ -63,7 +62,7 @@ packages = dvr_scan.docs.assets dvr_scan.docs.assets.javascripts dvr_scan.docs.assets.stylesheets -python_requires = >=3.9 +python_requires = >=3.10 include_package_data = True [options.entry_points] From c47dec02017c7b21c974f47487a6010f2ffb3fa0 Mon Sep 17 00:00:00 2001 From: Breakthrough Date: Sat, 8 Nov 2025 22:16:21 -0500 Subject: [PATCH 2/2] [app] Add slider to seek in source video #248 --- dvr_scan/app/region_editor.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/dvr_scan/app/region_editor.py b/dvr_scan/app/region_editor.py index 794cd67..07b23a6 100644 --- a/dvr_scan/app/region_editor.py +++ b/dvr_scan/app/region_editor.py @@ -187,6 +187,9 @@ def __init__( self._frame: np.ndarray = frame.copy() # Workspace self._frame_size: Size = Size(w=frame.shape[1], h=frame.shape[0]) self._original_frame: np.ndarray = frame.copy() # Copy to redraw on + self._video = cv2.VideoCapture(video_path) + self._video_pos = 0 + self._num_frames = int(self._video.get(cv2.CAP_PROP_FRAME_COUNT)) self._regions: ty.List[ty.List[Point]] = ( initial_shapes if initial_shapes else [initial_point_list(self._frame_size)] ) @@ -610,7 +613,17 @@ def select_region(index): # TODO: If Shift is held down, allow translating current shape # by left-click and drag. + def _seek(self, frame_num): + self._video_pos = int(float(frame_num)) + self._video.set(cv2.CAP_PROP_POS_FRAMES, self._video_pos) + ret, frame = self._video.read() + if ret: + self._source_frame = frame + self._rescale() + self._video_pos_label.config(text=f"{self._video_pos} / {self._num_frames}") + def _close(self, should_scan: bool): + self._video.release() if self._launched_from_app: self._root.destroy() self._on_close() @@ -702,6 +715,23 @@ def set_scale(val: str): self._scale_widget.grid(row=0, column=9, sticky="e", padx=8.0) tk.Label(frame, text="Zoom", anchor=tk.E).grid(row=0, column=8, sticky=tk.E, padx=(0, 8.0)) + ttk.Separator(self._root).grid(row=4, column=0, columnspan=2, sticky="ew") + video_frame = tk.Frame(self._root) + video_frame.grid(row=5, column=0, sticky="ew", columnspan=2) + video_frame.columnconfigure(1, weight=1) + self._video_pos_label = tk.Label(video_frame, text=f"0 / {self._num_frames}", anchor=tk.W) + self._video_pos_label.grid(row=0, column=0, sticky=tk.W, padx=8.0) + self._video_seek_widget = ttk.Scale( + video_frame, + orient=tk.HORIZONTAL, + length=200, + from_=0, + to=self._num_frames - 1, + command=self._seek, + value=0, + ) + self._video_seek_widget.grid(row=0, column=1, sticky="ew", padx=8.0) + self._bind_mouse() self._bind_keyboard() self._create_menubar()