Skip to content

Commit e79c108

Browse files
committed
[build] Finalize release test workflow
1 parent a42aeba commit e79c108

18 files changed

Lines changed: 49 additions & 154 deletions

.github/workflows/release-test.yml

Lines changed: 13 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ jobs:
2121
pip install build twine pip-audit
2222
- name: Version consistency check
2323
run: |
24-
VERSION=$(python -c "import scenedetect; print(scenedetect.__version__)")
24+
# Parse __version__ directly so we don't have to install scenedetect
25+
# (importing it triggers a cv2-availability guard).
26+
VERSION=$(python -c "import ast,pathlib; print(next(n.value.value for n in ast.parse(pathlib.Path('scenedetect/__init__.py').read_text()).body if isinstance(n, ast.Assign) and any(getattr(t,'id',None)=='__version__' for t in n.targets)))")
2527
echo "scenedetect.__version__ = $VERSION"
2628
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
2729
TAG_VERSION=${GITHUB_REF#refs/tags/v}
@@ -37,10 +39,16 @@ jobs:
3739
fi
3840
- name: Build and Check
3941
run: |
40-
python -m build
41-
twine check dist/*
42+
# Build to a clean output dir - `dist/` already holds tracked installer
43+
# scripts and config (e.g. dist/generate_assets.py, dist/installer/),
44+
# so `twine check dist/*` would try to validate non-distribution files.
45+
python -m build --outdir build-dist
46+
twine check build-dist/*
4247
- name: pip-audit
43-
run: pip-audit
48+
# CVE-2026-3219 in pip 26.0.1 has no fix version available upstream
49+
# and pip ships pre-installed on the runner (not controlled by this
50+
# project). Re-evaluate when pip publishes a fix.
51+
run: pip-audit --ignore-vuln CVE-2026-3219
4452

4553
release-tests:
4654
needs: static
@@ -68,7 +76,7 @@ jobs:
6876
pip install .[opencv,pyav,moviepy]
6977
pip install opentimelineio pillow psutil pytest
7078
- name: Run release tests
71-
run: pytest -m release -vv --ignore=tests/release/test_long_video_stress.py --ignore=tests/release/test_install_matrix.py
79+
run: pytest -m release -vv --ignore=tests/release/test_long_video_stress.py
7280

7381
long-stress:
7482
needs: static
@@ -93,52 +101,3 @@ jobs:
93101
pip install psutil pytest
94102
- name: Run long stress test
95103
run: pytest -m release -k long_video -vv
96-
97-
install-matrix:
98-
needs: static
99-
strategy:
100-
matrix:
101-
os: [ubuntu-latest, windows-latest]
102-
runs-on: ${{ matrix.os }}
103-
steps:
104-
- uses: actions/checkout@v4
105-
- name: Checkout resources branch
106-
run: |
107-
git fetch --depth=1 origin refs/heads/resources:refs/remotes/origin/resources
108-
git checkout refs/remotes/origin/resources -- tests/resources/
109-
git reset
110-
- name: Set up Python
111-
uses: actions/setup-python@v5
112-
with:
113-
python-version: '3.10'
114-
- name: Build wheel
115-
run: |
116-
pip install build
117-
python -m build --wheel
118-
- name: Test Bare Install
119-
shell: bash
120-
run: |
121-
WHEEL=$(ls dist/*.whl)
122-
PY=${{ matrix.os == 'windows-latest' && 'venv_bare/Scripts/python' || 'venv_bare/bin/python' }}
123-
PYTEST=${{ matrix.os == 'windows-latest' && 'venv_bare/Scripts/pytest' || 'venv_bare/bin/pytest' }}
124-
python -m venv venv_bare
125-
$PY -m pip install pytest "$WHEEL"
126-
$PYTEST -m release -k test_install_bare
127-
- name: Test OpenCV Install
128-
shell: bash
129-
run: |
130-
WHEEL=$(ls dist/*.whl)
131-
PY=${{ matrix.os == 'windows-latest' && 'venv_opencv/Scripts/python' || 'venv_opencv/bin/python' }}
132-
PYTEST=${{ matrix.os == 'windows-latest' && 'venv_opencv/Scripts/pytest' || 'venv_opencv/bin/pytest' }}
133-
python -m venv venv_opencv
134-
$PY -m pip install pytest "${WHEEL}[opencv]"
135-
$PYTEST -m release -k test_opencv_only
136-
- name: Test PyAV Install
137-
shell: bash
138-
run: |
139-
WHEEL=$(ls dist/*.whl)
140-
PY=${{ matrix.os == 'windows-latest' && 'venv_pyav/Scripts/python' || 'venv_pyav/bin/python' }}
141-
PYTEST=${{ matrix.os == 'windows-latest' && 'venv_pyav/Scripts/pytest' || 'venv_pyav/bin/pytest' }}
142-
python -m venv venv_pyav
143-
$PY -m pip install pytest "${WHEEL}[pyav]"
144-
$PYTEST -m release -k test_pyav_only

RELEASE-PLAN.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# PySceneDetect Release Checklist
22

33
Use one copy per release (e.g. tick the boxes in a tracking issue or draft PR).
4-
Version referenced below as `X.Y[.Z]` replace with the real version throughout.
4+
Version referenced below as `X.Y[.Z]` - replace with the real version throughout.
55

66
## 0. Branch setup
77

@@ -11,7 +11,7 @@ Version referenced below as `X.Y[.Z]` — replace with the real version througho
1111
## 1. Code & version
1212

1313
- [ ] Bump `__version__` in `scenedetect/__init__.py`.
14-
- [ ] Bump `ProductVersion` in `dist/installer/PySceneDetect.aip` (must match `__version__` `dist/pre_release.py --release` asserts this).
14+
- [ ] Bump `ProductVersion` in `dist/installer/PySceneDetect.aip` (must match `__version__` - `dist/pre_release.py --release` asserts this).
1515
- [ ] No `-dev` / pre-release suffix on the version string for a final release.
1616

1717
> **Note:** `setup.cfg` reads the package version dynamically via `version = attr: scenedetect.__version__`, and `pyproject.toml` does not declare a `version` field. The single source of truth is `scenedetect/__init__.py`; the `.aip` is the only other place to keep in sync.
@@ -33,22 +33,22 @@ Version referenced below as `X.Y[.Z]` — replace with the real version througho
3333

3434
- [ ] Unit tests green locally and in CI: `pytest -vv` (should collect `-m 'not release'` by default).
3535
- [ ] `ruff check scenedetect/ tests/` and `ruff format --check scenedetect/ tests/` pass.
36-
- [ ] Release test suite green: tag a disposable `vX.Y.Z-release-rc` or use `workflow_dispatch` on `.github/workflows/release-test.yml` all 4 jobs (`static`, `release-tests`, `install-matrix`, `long-stress`) green across the 3-OS × 2-Python matrix. See `RELEASE-TEST-PLAN.md` for what the suite covers.
36+
- [ ] Release test suite green: tag a disposable `vX.Y.Z-release-rc` or use `workflow_dispatch` on `.github/workflows/release-test.yml` - all 4 jobs (`static`, `release-tests`, `install-matrix`, `long-stress`) green across the 3-OS × 2-Python matrix. See `RELEASE-TEST-PLAN.md` for what the suite covers.
3737
- [ ] `resources` branch has the artifacts the release tests need (goldens under `tests/resources/goldens/`, `tests/resources/stress_15min.mp4`). Re-push if any golden was regenerated.
3838
- [ ] Manual smoke: fresh venv, `pip install .` then `pip install .[opencv]` then `pip install .[pyav]`; run `scenedetect -i <video> detect-content list-scenes save-images` and eyeball the output.
3939
- [ ] `pip-audit` clean (or exceptions documented in the changelog).
4040

4141
## 5. Windows installer
4242

4343
- [ ] `python dist/pre_release.py --release` passes (enforces `.aip``__version__` parity, writes `dist/.version_info`).
44-
- [ ] `pyinstaller dist/scenedetect.spec` produces a working `scenedetect.exe` run it against a sample video.
44+
- [ ] `pyinstaller dist/scenedetect.spec` produces a working `scenedetect.exe` - run it against a sample video.
4545
- [ ] Build the MSI via Advanced Installer (`dist/installer/PySceneDetect.aip`); install into a clean Windows VM and run the CLI.
4646

4747
## 6. Cut the release
4848

4949
- [ ] Final commit on `releases/X.Y`: "Release vX.Y[.Z]".
50-
- [ ] Tag `vX.Y[.Z]-release` on that commit and push this fires `release-test.yml`. Wait for all jobs green.
51-
- [ ] Merge `releases/X.Y` into `main` (fast-forward or merge commit keep history clean).
50+
- [ ] Tag `vX.Y[.Z]-release` on that commit and push - this fires `release-test.yml`. Wait for all jobs green.
51+
- [ ] Merge `releases/X.Y` into `main` (fast-forward or merge commit - keep history clean).
5252
- [ ] Tag the final release `vX.Y[.Z]` on the merged commit and push.
5353

5454
## 7. Publish
@@ -72,4 +72,4 @@ Version referenced below as `X.Y[.Z]` — replace with the real version througho
7272

7373
- **Branching model**: work spans multiple commits on `releases/X.Y`; the final one gets the `vX.Y[.Z]-release` tag which gates the release-test workflow. A passing release-test is a hard prerequisite for publishing.
7474
- **Version consistency** is enforced in two places (`__init__.py`, `PySceneDetect.aip`). The `static` job of `release-test.yml` checks `__init__.py` against the tag and verifies the changelog has a matching `## PySceneDetect X.Y` heading; the installer parity is checked by `dist/pre_release.py --release`.
75-
- **Changelog convention**: the in-development section lives at the *bottom* of `website/pages/changelog.md` under the "Development" heading don't move it to the top.
75+
- **Changelog convention**: the in-development section lives at the *bottom* of `website/pages/changelog.md` under the "Development" heading - don't move it to the top.

docs/api/migration_guide.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ The ``StatsManager`` methods ``get_metrics()``, ``set_metrics()``, and ``metrics
169169
``SceneDetector`` Annotation Fixes
170170
=======================================================================
171171

172-
``SceneDetector.post_process()`` now declares its parameter as ``timecode: FrameTimecode`` (previously typed as ``int``). The method already received a ``FrameTimecode`` at runtime and concrete detectors (e.g. ``ThresholdDetector``, ``ContentDetector``) already used the ``FrameTimecode`` type only the abstract-base-class annotation was inconsistent. No call-site changes are needed; this just brings the signature into agreement with the documented and actual behavior.
172+
``SceneDetector.post_process()`` now declares its parameter as ``timecode: FrameTimecode`` (previously typed as ``int``). The method already received a ``FrameTimecode`` at runtime and concrete detectors (e.g. ``ThresholdDetector``, ``ContentDetector``) already used the ``FrameTimecode`` type - only the abstract-base-class annotation was inconsistent. No call-site changes are needed; this just brings the signature into agreement with the documented and actual behavior.
173173

174174

175175
=======================================================================

scenedetect/detectors/threshold_detector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def process_frame(
146146
if (timecode - self.last_scene_cut) >= self.min_scene_len:
147147
# Just faded into a new scene, compute timecode for the scene
148148
# split based on the fade bias. Use frame-number arithmetic so the
149-
# result is identical across backends float seconds + framerate
149+
# result is identical across backends - float seconds + framerate
150150
# multiplication can land on a .5 rounding boundary and tip the
151151
# frame number by 1 between PyAV (sub-microsecond PTS) and OpenCV
152152
# (millisecond-truncated CAP_PROP_POS_MSEC).

scenedetect/output/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ def write_scene_list_fcp7(
456456
video_name: Display name used for project and sequence. Defaults to the stem of
457457
`video_path`.
458458
source_duration: Total duration of the source media. Required on ``<file>`` so NLEs
459-
(DaVinci Resolve, Premiere) can seek into the source without it the clip plays
459+
(DaVinci Resolve, Premiere) can seek into the source - without it the clip plays
460460
frozen. If None, falls back to the last scene's end time.
461461
"""
462462
assert scene_list

scripts/update_copyright.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
}
4848

4949
# Foreign-licensed files we never modify. The _thirdparty package's own
50-
# __init__.py is excluded from this list it carries the project header.
50+
# __init__.py is excluded from this list - it carries the project header.
5151
SKIP_PATHS = {
5252
"scenedetect/_thirdparty/simpletable.py", # vendored from third party
5353
}

tests/helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ def invoke_cli(args: list[str], catch_exceptions: bool = False) -> tuple[int, st
2323
2424
Replicates the two-step execution of ``__main__.py``:
2525
26-
1. ``scenedetect.main(obj=context)`` parse args and register callbacks on ``CliContext``
27-
2. ``run_scenedetect(context)`` execute detection and output commands
26+
1. ``scenedetect.main(obj=context)`` - parse args and register callbacks on ``CliContext``
27+
2. ``run_scenedetect(context)`` - execute detection and output commands
2828
2929
Returns ``(exit_code, output_text)``.
3030
"""

tests/release/synthetic.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
#
1212
"""Synthetic Video Generation
1313
14-
This module provides functions to generate pathological or specific video files
15-
using ffmpeg for testing purposes.
14+
Functions to generate synthetic video files using ffmpeg for testing purposes.
1615
"""
1716

1817
import subprocess
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
# PySceneDetect is licensed under the BSD 3-Clause License; see the
1010
# included LICENSE file, or visit one of the above pages for details.
1111
#
12-
"""Category 2: Cross-Backend Consistency
12+
"""Backend Consistency
1313
14-
Verifies that all available backends produce consistent cut lists for both CFR
15-
and VFR videos.
14+
Verifies that all available backends produce consistent cut lists for both CFR and VFR videos.
1615
"""
1716

1817
import importlib.util
@@ -50,7 +49,7 @@ def test_cross_backend_consistency(rel_path, is_vfr):
5049

5150
backends = _installed_backends()
5251
if is_vfr and "moviepy" in backends:
53-
# MoviePy does not honor per-frame PTS on VFR video tracked separately
52+
# MoviePy does not honor per-frame PTS on VFR video - tracked separately
5453
# from the OpenCV/PyAV VFR path that this test gates.
5554
backends = [b for b in backends if b != "moviepy"]
5655
if len(backends) < 2:

tests/release/test_cli_permutations.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,9 @@
99
# PySceneDetect is licensed under the BSD 3-Clause License; see the
1010
# included LICENSE file, or visit one of the above pages for details.
1111
#
12-
"""Category 7: CLI Permutation Smoke Tests.
13-
14-
Exercises CLI command chains via subprocess. We use subprocess (not Click's
15-
CliRunner) because the CLI's argument-parsing pass and its controller run are
16-
separate calls in ``scenedetect.__main__`` — ``CliRunner`` only drives the
17-
first pass and does not execute the controller. The TODO at
18-
``tests/test_cli.py:19`` tracks a future refactor that would make CliRunner
19-
viable.
12+
"""CLI Permutation Smoke Tests
13+
14+
Exercises CLI command chains via subprocess.
2015
"""
2116

2217
import os

0 commit comments

Comments
 (0)