Skip to content

Commit c8b3a3d

Browse files
committed
[build] Cache installer & ffmpeg on signed builder
1 parent 5d5bcac commit c8b3a3d

8 files changed

Lines changed: 63 additions & 65 deletions

RELEASE-PLAN.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Version referenced below as `X.Y[.Z]` - replace with the real version throughout
1111
## 1. Code & version
1212

1313
- [ ] Bump `__version__` in `scenedetect/__init__.py`.
14-
- [ ] Bump the installer project: `python scripts/bump_installer.py` (rewrites `ProductVersion`, regenerates `ProductCode`, updates the MSI filename via the AdvancedInstaller CLI). Add `--sync-files` after `pyinstaller` if any bundled dependency versions changed since the last release - this re-syncs APPDIR from `dist/scenedetect/` and replaces the manual "delete install dir + re-add files" GUI step. `scripts/pre_release.py --release` asserts the resulting `ProductVersion` matches `__version__`.
14+
- [ ] Bump the installer project: `python scripts/update_installer.py` (rewrites `ProductVersion`, regenerates `ProductCode`, updates the MSI filename via the AdvancedInstaller CLI). Add `--sync-files` after `pyinstaller` if any bundled dependency versions changed since the last release - this re-syncs APPDIR from `dist/scenedetect/` and replaces the manual "delete install dir + re-add files" GUI step. `scripts/pre_release.py --release` asserts the resulting `ProductVersion` matches `__version__`.
1515
- [ ] No `-dev` / pre-release suffix on the version string for a final release.
1616

1717
> **Note:** `pyproject.toml` does not declare a `version` field - the single source of truth is `scenedetect/__init__.py`; the Windows installer `.aip` is the only other place to keep in sync.
@@ -42,19 +42,19 @@ Version referenced below as `X.Y[.Z]` - replace with the real version throughout
4242

4343
- [ ] `python scripts/pre_release.py --release` passes (enforces `.aip` <-> `__version__` parity, writes `packaging/windows/.version_info`).
4444
- [ ] `pyinstaller packaging/windows/scenedetect.spec` produces a working `scenedetect.exe` - run it against a sample video.
45-
- [ ] `python scripts/stage_windows_dist.py --ffmpeg-dir <dir> --portable-zip` populates `dist/scenedetect/` with ffmpeg, third-party licenses, sphinx docs, and emits the portable `.zip`. Pass `--ffmpeg-dir` pointing at a recent extracted [GyanD codexffmpeg](https://github.com/GyanD/codexffmpeg/releases) build; omit it only for offline builds (uses the bundled `packaging/windows/thirdparty.7z` with a stub `LICENSE-FFMPEG`).
46-
- [ ] `python scripts/bump_installer.py --sync-files` and commit the .aip diff (refreshes the APPDIR baseline so CI's per-build `--sync-only` diff stays small).
45+
- [ ] `python scripts/stage_windows_dist.py --ffmpeg-dir <dir>` populates `dist/scenedetect/` with ffmpeg, third-party licenses, sphinx docs, and emits the portable `.zip`. Pass `--ffmpeg-dir` pointing at a recent extracted [GyanD codexffmpeg](https://github.com/GyanD/codexffmpeg/releases) build; omit it only for offline builds (uses the bundled `packaging/windows/thirdparty.7z` with a stub `LICENSE-FFMPEG`).
46+
- [ ] `python scripts/update_installer.py --sync-files` and commit the .aip diff (refreshes the APPDIR baseline so CI's per-build `--sync-only` diff stays small).
4747
- [ ] Build the MSI via Advanced Installer (`packaging/windows/installer/PySceneDetect.aip`); install into a clean Windows VM and run the CLI.
4848
- [ ] After both `pyinstaller` and the MSI build are done (and the portable `.zip` is staged at `dist/PySceneDetect-X.Y.Z-portable.zip`), run `python scripts/generate_manifest.py` to produce `dist/PySceneDetect-X.Y.Z.manifest.json` (per-file SHA256 audit of every artifact) and `dist/SHA256SUMS` (flat `sha256sum -c` compatible). Both are attached to the GitHub release in step 7.
4949

50-
> **GUI required for structural changes.** `scripts/bump_installer.py` covers routine version bumps and `--sync-files` covers dependency-driven file-list changes, but anything that touches the *project structure* of the .aip still needs the AdvancedInstaller GUI. Examples:
50+
> **GUI required for structural changes.** `scripts/update_installer.py` covers routine version bumps and `--sync-files` covers dependency-driven file-list changes, but anything that touches the *project structure* of the .aip still needs the AdvancedInstaller GUI. Examples:
5151
>
5252
> - Moving the .aip or its source tree (the build's `SourcePath` references are stored relative to the .aip and aren't rewritten by `/NewSync` - cf. the `dist/installer/` -> `packaging/windows/installer/` move that broke the relative paths until they were edited in the GUI).
5353
> - Adding/removing build configurations, features, or prerequisites.
5454
> - Editing dialog layouts, branding bitmaps, install sequences, custom actions, file associations, or shortcuts.
5555
> - Changing `UpgradeCode`, install directory layout (`APPDIR` location), or per-component attributes.
5656
>
57-
> When in doubt, open the .aip in AdvancedInstaller, make the change, save, and commit the resulting diff. Re-run `bump_installer.py` afterwards if the version-identity fields need refreshing.
57+
> When in doubt, open the .aip in AdvancedInstaller, make the change, save, and commit the resulting diff. Re-run `update_installer.py` afterwards if the version-identity fields need refreshing.
5858
5959
## 6. Cut the release
6060

appveyor.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
build: false
44

5+
cache:
6+
- 'ffmpeg-%ffmpeg_version%-full_build.7z -> appveyor.yml'
7+
- 'packaging\windows\installer\advinst.msi -> appveyor.yml'
8+
- '%LOCALAPPDATA%\uv\cache -> pyproject.toml'
9+
510
# Branches applies to tags as well. We only build on tagged releases of the form vX.Y.Z-release
611
branches:
712
only:
@@ -37,10 +42,10 @@ install:
3742
- echo * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
3843
- 'SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%'
3944
- python --version
40-
- python -m pip install --upgrade pip build wheel virtualenv setuptools
41-
- python -m pip install .[docs]
42-
- python -m pip install --upgrade -r packaging/windows/requirements.txt --no-binary imageio-ffmpeg
43-
- appveyor DownloadFile https://github.com/GyanD/codexffmpeg/releases/download/%ffmpeg_version%/ffmpeg-%ffmpeg_version%-full_build.7z
45+
- python -m pip install uv
46+
- uv pip install --system .[docs]
47+
- uv pip install --system -r packaging/windows/requirements.txt --no-binary imageio-ffmpeg
48+
- if not exist ffmpeg-%ffmpeg_version%-full_build.7z appveyor DownloadFile https://github.com/GyanD/codexffmpeg/releases/download/%ffmpeg_version%/ffmpeg-%ffmpeg_version%-full_build.7z
4449
- 7z e ffmpeg-%ffmpeg_version%-full_build.7z -odist/ffmpeg ffmpeg.exe LICENSE -r
4550
# moviepy.config reads FFMPEG_BINARY (which routes through imageio_ffmpeg) at import time.
4651
# `--no-binary imageio-ffmpeg` strips the bundled ffmpeg, so point it at the GyanD copy
@@ -57,7 +62,7 @@ install:
5762
# portable .zip - keeps CI and local builds in sync (see scripts/stage_windows_dist.py).
5863
- python scripts/pre_release.py --release
5964
- pyinstaller packaging/windows/scenedetect.spec
60-
- python scripts/stage_windows_dist.py --ffmpeg-dir dist/ffmpeg --portable-zip
65+
- python scripts/stage_windows_dist.py --ffmpeg-dir dist/ffmpeg
6166

6267
- echo * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
6368
- echo * * BUILDING MSI INSTALLER * *
@@ -66,7 +71,7 @@ install:
6671
- cd packaging/windows/installer
6772
- ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1'))
6873
- appveyor-tools\secure-file -decrypt license65.dat.enc -secret %ai_license_secret% -salt %ai_license_salt%
69-
- appveyor DownloadFile https://www.advancedinstaller.com/downloads/advinst.msi
74+
- if not exist advinst.msi appveyor DownloadFile https://www.advancedinstaller.com/downloads/advinst.msi
7075
- msiexec /i advinst.msi /qn
7176
# Resolve the installed Advanced Installer bin path dynamically - the upstream
7277
# MSI is unversioned so the directory name (Advanced Installer X.Y.Z) drifts.
@@ -82,7 +87,7 @@ install:
8287
# release tag and must stay stable across rebuilds for upgrade-chain integrity.
8388
# On non-tag builds, also pass --dev so the MSI is named PySceneDetect-{ver}-dev-win64.msi
8489
# (keeps dev artifacts distinguishable from signed releases).
85-
- if "%APPVEYOR_REPO_TAG%"=="true" (python scripts/bump_installer.py --sync-only) else (python scripts/bump_installer.py --sync-only --dev)
90+
- if "%APPVEYOR_REPO_TAG%"=="true" (python scripts/update_installer.py --sync-only) else (python scripts/update_installer.py --sync-only --dev)
8691
# Snapshot the post-sync .aip and the actual payload tree as build artifacts.
8792
# The committed .aip is a baseline; CI adapts it to its own pyinstaller output
8893
# and we never write back to git, so these snapshots are the authoritative

scripts/generate_assets.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
#!/usr/bin/env python
2-
"""Generate pyscenedetect.ico and logo PNGs from SVG sources.
3-
4-
Requires Inkscape (for SVG rasterization) and Pillow (for ICO generation).
5-
"""
1+
#
2+
# PySceneDetect: Python-Based Video Scene Detector
3+
# -------------------------------------------------------------------
4+
# [ Site: https://scenedetect.com ]
5+
# [ Docs: https://scenedetect.com/docs/ ]
6+
# [ Github: https://github.com/Breakthrough/PySceneDetect/ ]
7+
#
8+
# Copyright (C) 2026 Brandon Castellano <http://www.bcastell.com>.
9+
# PySceneDetect is licensed under the BSD 3-Clause License; see the
10+
# included LICENSE file, or visit one of the above pages for details.
11+
#
12+
"""Generate pyscenedetect.ico and logo PNGs from SVG sources. Requires Inkscape and Pillow."""
613

714
import contextlib
815
import shutil

scripts/generate_goldens.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
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-
"""Golden Cut-List Generator
13-
14-
This script generates golden cut-lists in JSON format for the release test suite.
15-
"""
12+
"""Generates golden cut-lists in JSON format for the release test suite."""
1613

1714
import argparse
1815
import json

scripts/generate_manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939

4040
def msi_version(raw: str) -> str:
41-
# Mirror scripts/bump_installer.py - the artifact filename uses the
41+
# Mirror scripts/update_installer.py - the artifact filename uses the
4242
# normalized X.Y.Z form, not the Python __version__ string.
4343
parts = [re.split(r"[^\d]", p, maxsplit=1)[0] for p in raw.split(".")]
4444
while len(parts) < 3:

scripts/pre_release.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
# included LICENSE file, or visit one of the above pages for details.
1111
#
1212

13-
# Pre-release script to run before invoking `pyinstaller`:
14-
#
15-
# python scripts/pre_release.py
16-
# pyinstaller packaging/windows/scenedetect.spec
17-
#
13+
"""
14+
Pre-release script to run before invoking `pyinstaller` when building the Windows distribution:
15+
```bash
16+
python scripts/pre_release.py
17+
pyinstaller packaging/windows/scenedetect.spec
18+
```
19+
"""
1820
import sys
1921
from pathlib import Path
2022

@@ -23,7 +25,7 @@
2325
sys.path.insert(0, str(REPO_DIR))
2426
sys.path.insert(0, str(SCRIPTS_DIR))
2527

26-
from bump_installer import msi_version # noqa: E402
28+
from update_installer import msi_version # noqa: E402
2729

2830
import scenedetect # noqa: E402
2931

@@ -40,12 +42,12 @@
4042
installer_aip = INSTALLER_AIP.read_text()
4143
# The .aip stores the numeric MSI form (e.g. "0.7.0"), not the Python __version__
4244
# (which may be "0.7-dev0", "0.7", "0.7.1", ...). Normalize through the same
43-
# function bump_installer.py uses to write the .aip so the comparison is apples-to-apples.
45+
# function update_installer.py uses to write the .aip so the comparison is apples-to-apples.
4446
expected = msi_version(VERSION)
4547
aip_row = f'<ROW Property="ProductVersion" Value="{expected}" Options="32"/>'
4648
assert aip_row in installer_aip, (
4749
f"Installer ProductVersion does not match normalized {VERSION!r} ({expected!r}). "
48-
f"Run `python scripts/bump_installer.py` to refresh the .aip."
50+
f"Run `python scripts/update_installer.py` to refresh the .aip."
4951
)
5052

5153
with VERSION_INFO.open("wb") as f:

scripts/stage_windows_dist.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,36 @@
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-
"""Stage non-pyinstaller assets into dist/scenedetect/.
12+
"""Stages Windows distribution assets into dist/scenedetect/.
1313
14-
Pyinstaller produces only scenedetect.exe + _internal/. This script adds
15-
the rest of what both the AdvancedInstaller MSI and the portable ZIP need:
16-
ffmpeg.exe + its LICENSE, the project LICENSE / README.txt, third-party
17-
licenses, and sphinx-built docs. Mirrors the inline staging steps that
18-
appveyor.yml used to do, so CI and local builds stay in sync.
19-
20-
Sequence in a release:
14+
Sequence in a release to generate the installer:
2115
16+
```bash
2217
python scripts/pre_release.py
2318
pyinstaller packaging/windows/scenedetect.spec
24-
python scripts/stage_windows_dist.py --ffmpeg-dir <dir> --portable-zip
25-
python scripts/bump_installer.py --sync-files
19+
python scripts/stage_windows_dist.py --ffmpeg-dir <dir>
20+
python scripts/update_installer.py --sync-files
2621
AdvancedInstaller.com /build packaging/windows/installer/PySceneDetect.aip
2722
python scripts/generate_manifest.py
23+
```
2824
29-
--ffmpeg-dir points at a directory containing ffmpeg.exe and its LICENSE
30-
(e.g. the extracted GyanD codexffmpeg release). If omitted, the script
31-
falls back to extracting ffmpeg.exe from packaging/windows/thirdparty.7z;
32-
LICENSE-FFMPEG is then a stub since the bundled archive doesn't carry it.
25+
This script assumes it is run on a Windows machine.
3326
"""
3427

28+
# TODO: This should be called from the Github Actions workflow as well, right now it's only
29+
# done from the appveyor one. When that's done it should be merged with update_installer.py
30+
# into a combined "prepare_windows_dist.py".
31+
3532
import argparse
3633
import re
3734
import shutil
3835
import subprocess
3936
import sys
4037
import zipfile
38+
39+
if sys.platform != "win32":
40+
print("Error: stage_windows_dist.py must be run on Windows.", file=sys.stderr)
41+
sys.exit(1)
4142
from pathlib import Path
4243

4344
REPO_DIR = Path(__file__).resolve().parent.parent
@@ -174,11 +175,6 @@ def main() -> None:
174175
help="Directory containing ffmpeg.exe and its LICENSE. "
175176
"If omitted, ffmpeg is extracted from packaging/windows/thirdparty.7z.",
176177
)
177-
parser.add_argument(
178-
"--portable-zip",
179-
action="store_true",
180-
help="Also produce dist/PySceneDetect-<version>-portable.zip.",
181-
)
182178
args = parser.parse_args()
183179

184180
if not DIST_TREE.exists():
@@ -191,9 +187,7 @@ def main() -> None:
191187
copy_file(PACKAGING_WIN / "README.txt", DIST_TREE / "README.txt")
192188
stage_thirdparty_licenses()
193189
build_docs()
194-
195-
if args.portable_zip:
196-
make_portable_zip(msi_version(scenedetect.__version__))
190+
make_portable_zip(msi_version(scenedetect.__version__))
197191

198192

199193
if __name__ == "__main__":
Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,14 @@
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-
"""Bump the AdvancedInstaller .aip project for a release.
12+
"""Update the AdvancedInstaller .aip project for a release.
1313
1414
Usage:
15-
python scripts/bump_installer.py # version bump only
16-
python scripts/bump_installer.py --sync-files # bump + re-sync APPDIR
17-
python scripts/bump_installer.py --sync-only # re-sync APPDIR only (CI)
18-
python scripts/bump_installer.py --sync-only --dev # CI dev build (renames MSI)
19-
python scripts/bump_installer.py --version 0.7.0 # explicit version override
20-
21-
The committed .aip is a baseline; CI's --sync-only adapts it per build and is
22-
never written back to git. Refresh locally with --sync-files before each release.
23-
24-
All paths shell out to AdvancedInstaller.com to preserve .aip invariants
25-
(line endings, attribute ordering, GUID casing). Override CLI discovery with
26-
the ADVINST environment variable.
15+
python scripts/update_installer.py # version bump only
16+
python scripts/update_installer.py --sync-files # bump + re-sync APPDIR
17+
python scripts/update_installer.py --sync-only # re-sync APPDIR only (CI)
18+
python scripts/update_installer.py --sync-only --dev # CI dev build (renames MSI)
19+
python scripts/update_installer.py --version 0.7.0 # explicit version override
2720
"""
2821

2922
import argparse

0 commit comments

Comments
 (0)