Skip to content

Commit 13da88a

Browse files
authored
Merge branch 'main' into add-backend-test
2 parents 499818a + 98f4be6 commit 13da88a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+973
-582
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,11 @@ jobs:
2626
runs-on: ${{ matrix.os }}
2727
strategy:
2828
matrix:
29-
os: [macos-13, macos-14, ubuntu-22.04, ubuntu-latest, windows-latest]
30-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
31-
exclude:
32-
# macos-14 builders use M1 (ARM64) which does not have a Python 3.7 package available.
33-
- os: macos-14
34-
python-version: "3.7"
35-
# ubuntu 24+ does not have Python 3.7
36-
- os: ubuntu-latest
37-
python-version: "3.7"
38-
29+
os: [macos-14, macos-latest, ubuntu-22.04, ubuntu-latest, windows-latest]
30+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
3931
env:
4032
# Version is extracted below and used to find correct package install path.
4133
scenedetect_version: ""
42-
# Setuptools must be pinned for the Python 3.7 builders.
43-
setuptools_version: "${{ matrix.python-version == '3.7' && '==62.3.4' || '' }}"
4434

4535
steps:
4636
- uses: actions/checkout@v4
@@ -61,7 +51,7 @@ jobs:
6151

6252
- name: Install Dependencies
6353
run: |
64-
python -m pip install --upgrade pip build wheel virtualenv setuptools${{ env.setuptools_version }}
54+
python -m pip install --upgrade pip build wheel virtualenv setuptools
6555
pip install -r requirements_headless.txt --only-binary av,opencv-python-headless
6656
6757
- name: Install MoviePy

.github/workflows/generate-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
env:
1717
# TODO: Figure out a better way to handle figuring out what version /latest should be,
1818
# e.g. add a latest version file in main.
19-
scenedetect_docs_latest: '0.6.6'
19+
scenedetect_docs_latest: '0.6.7'
2020
scenedetect_docs_dest: ''
2121

2222
steps:

.github/workflows/publish-pypi.yml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: Publish PyPI Package
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
description: 'Tag To Publish'
8+
required: true
9+
environment:
10+
description: 'PyPI Environment'
11+
required: true
12+
type: choice
13+
options:
14+
- test
15+
- release
16+
default: 'test'
17+
18+
jobs:
19+
verify:
20+
name: Verify Build
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Check workflows
24+
uses: actions/github-script@v6
25+
with:
26+
script: |
27+
const { owner, repo } = context.repo;
28+
const tag = "${{ github.event.inputs.tag }}";
29+
const requiredWorkflows = ['Windows Distribution', 'Python Distribution'];
30+
let workflowConclusions = {};
31+
32+
console.log(`Checking for successful workflow runs for tag: ${tag}`);
33+
34+
const { data: response } = await github.rest.actions.listWorkflowRunsForRepo({
35+
owner,
36+
repo,
37+
event: 'push',
38+
});
39+
40+
const runsForTag = response.workflow_runs.filter(run => run.head_branch === tag);
41+
42+
for (const run of runsForTag) {
43+
if (requiredWorkflows.includes(run.name)) {
44+
if (!workflowConclusions[run.name] || new Date(run.created_at) > new Date(workflowConclusions[run.name].created_at)) {
45+
workflowConclusions[run.name] = {
46+
conclusion: run.conclusion,
47+
created_at: run.created_at,
48+
html_url: run.html_url,
49+
};
50+
}
51+
}
52+
}
53+
54+
let allSuccess = true;
55+
for (const workflowName of requiredWorkflows) {
56+
if (!workflowConclusions[workflowName]) {
57+
core.setFailed(`Workflow "${workflowName}" was not found for tag ${tag}.`);
58+
allSuccess = false;
59+
} else if (workflowConclusions[workflowName].conclusion !== 'success') {
60+
core.setFailed(`Workflow "${workflowName}" did not succeed for tag ${tag}. Conclusion was "${workflowConclusions[workflowName].conclusion}". See: ${workflowConclusions[workflowName].html_url}`);
61+
allSuccess = false;
62+
} else {
63+
console.log(`✅ Workflow "${workflowName}" succeeded for tag ${tag}.`);
64+
}
65+
}
66+
67+
if (!allSuccess) {
68+
throw new Error("One or more required build workflows did not succeed.");
69+
}
70+
71+
publish:
72+
name: Building and Publishing to ${{ github.event.inputs.environment }} PyPI
73+
runs-on: ubuntu-latest
74+
needs: verify
75+
76+
environment:
77+
name: ${{ github.event.inputs.environment == 'test' && 'test' || 'release' }}
78+
url: ${{ github.event.inputs.environment == 'test' && 'https://test.pypi.org/p/scenedetect' || 'https://pypi.org/p/scenedetect' }}
79+
80+
permissions:
81+
id-token: write # IMPORTANT: mandatory for trusted publishing
82+
83+
steps:
84+
- name: Checkout ${{ github.event.inputs.tag }}
85+
uses: actions/checkout@v3
86+
with:
87+
ref: ${{ github.event.inputs.tag }}
88+
89+
- name: Set up Python
90+
uses: actions/setup-python@v3
91+
with:
92+
python-version: "3.x"
93+
94+
- name: Install Dependencies
95+
run: |
96+
python -m pip install --upgrade pip
97+
pip install build twine
98+
99+
- name: Build Package
100+
run: |
101+
python -m build
102+
mkdir pkg
103+
mv dist/*.tar.gz pkg/
104+
mv dist/*.whl pkg/
105+
106+
- name: Upload Package
107+
uses: actions/upload-artifact@v4
108+
with:
109+
name: scenedetect-dist
110+
path: |
111+
pkg/*.tar.gz
112+
pkg/*.whl
113+
114+
- name: Publish Package
115+
uses: pypa/gh-action-pypi-publish@release/v1
116+
with:
117+
repository-url: ${{ github.event.inputs.environment == 'test' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
118+
packages-dir: pkg/
119+
print-hash: true

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Video Cut Detection and Analysis Tool
1111

1212
----------------------------------------------------------
1313

14-
### Latest Release: v0.6.6 (March 9, 2025)
14+
### Latest Release: v0.6.7 (August 24, 2025)
1515

1616
**Website**: [scenedetect.com](https://www.scenedetect.com)
1717

@@ -66,8 +66,8 @@ scene_list = detect('my_video.mp4', ContentDetector())
6666
for i, scene in enumerate(scene_list):
6767
print(' Scene %2d: Start %s / Frame %d, End %s / Frame %d' % (
6868
i+1,
69-
scene[0].get_timecode(), scene[0].get_frames(),
70-
scene[1].get_timecode(), scene[1].get_frames(),))
69+
scene[0].get_timecode(), scene[0].frame_num,
70+
scene[1].get_timecode(), scene[1].frame_num,))
7171
```
7272

7373
We can also split the video into each scene if `ffmpeg` is installed (`mkvmerge` is also supported):

appveyor.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ environment:
2020
secure: of3o1pInqCJYwKLFsiadbsRYazCmCuZq7r2roaYvYXmBvm6e6JHsRU47waylTmhm
2121
ai_license_salt:
2222
secure: +NKWwlkEptlThgfeL35pLo7EsnkJc+4WODm8tTg1aO5fc0duQ4r100fHQYj6nzhyUdy3Dhs/mOLkxD8rNbBiEQ==
23+
ffmpeg_version: "8.0"
2324

2425
# SignPath Config for Code Signing
2526
deploy:
@@ -41,8 +42,8 @@ install:
4142
- python -m pip install --upgrade -r dist/requirements_windows.txt --no-binary imageio-ffmpeg
4243
# Checkout build resources and third party software used for testing.
4344
- git checkout refs/remotes/origin/resources -- dist/
44-
- appveyor DownloadFile https://github.com/GyanD/codexffmpeg/releases/download/7.1/ffmpeg-7.1-full_build.7z
45-
- 7z e ffmpeg-7.1-full_build.7z -odist/ffmpeg ffmpeg.exe LICENSE -r
45+
- appveyor DownloadFile https://github.com/GyanD/codexffmpeg/releases/download/%ffmpeg_version%/ffmpeg-%ffmpeg_version%-full_build.7z
46+
- 7z e ffmpeg-%ffmpeg_version%-full_build.7z -odist/ffmpeg ffmpeg.exe LICENSE -r
4647
- 'SET IMAGEIO_FFMPEG_EXE=%APPVEYOR_BUILD_FOLDER%\\dist\\ffmpeg\\ffmpeg.exe'
4748

4849
- echo * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -72,7 +73,7 @@ install:
7273
- appveyor-tools\secure-file -decrypt license65.dat.enc -secret %ai_license_secret% -salt %ai_license_salt%
7374
- appveyor DownloadFile https://www.advancedinstaller.com/downloads/advinst.msi
7475
- msiexec /i advinst.msi /qn
75-
- 'SET PATH=%PATH%;C:\\Program Files (x86)\\Caphyon\\Advanced Installer 22.5\\bin\\x86'
76+
- 'SET PATH=%PATH%;C:\\Program Files (x86)\\Caphyon\\Advanced Installer 22.9.1\\bin\\x86'
7677
# License path must be absolute
7778
- AdvancedInstaller.com /RegisterOffline "%cd%\license65.dat"
7879
# Create MSI installer
@@ -98,7 +99,9 @@ test_script:
9899
- git checkout refs/remotes/origin/resources -- tests/resources/
99100
- move dist\scenedetect\ffmpeg.exe ffmpeg.exe
100101
# Run unit tests
101-
- pytest
102+
# TODO: We are at the new build time limit for this plan apparently, 10 mins. Figure out a
103+
# strategy to deal with that (see if we can use Github as a builder?).
104+
# - pytest
102105
# Test Windows build
103106
- move ffmpeg.exe dist\scenedetect\ffmpeg.exe
104107
- cd dist/scenedetect

dist/installer/PySceneDetect.aip

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
<ROW Property="DialogBitmap" Value="dialog.jpg" MultiBuildValue="DefaultBuild:installer_logo.jpg" Type="1" MsiKey="DialogBitmap"/>
2424
<ROW Property="MSIFASTINSTALL" MultiBuildValue="DefaultBuild:3"/>
2525
<ROW Property="Manufacturer" Value="Brandon Castellano"/>
26-
<ROW Property="ProductCode" Value="1033:{5F017601-DFDD-4381-A35F-79634ED91632} " Type="16"/>
26+
<ROW Property="ProductCode" Value="1033:{AA389A8A-489B-403A-9EF6-B8AF31DE1D0D} " Type="16"/>
2727
<ROW Property="ProductLanguage" Value="1033"/>
2828
<ROW Property="ProductName" Value="PySceneDetect"/>
29-
<ROW Property="ProductVersion" Value="0.6.6" Options="32"/>
29+
<ROW Property="ProductVersion" Value="0.6.7" Options="32"/>
3030
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND;AI_SETUPEXEPATH;SETUPEXEDIR"/>
3131
<ROW Property="UpgradeCode" Value="{D0AF0419-DBD5-455C-A4AC-4518A35DD23E}"/>
3232
<ROW Property="WindowsType9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
@@ -125,7 +125,7 @@
125125
<ROW Directory="wheel0.45.1.distinfo_Dir" Directory_Parent="_internal_Dir" DefaultDir="WHEEL-~1.DIS|wheel-0.45.1.dist-info"/>
126126
</COMPONENT>
127127
<COMPONENT cid="caphyon.advinst.msicomp.SideBySideGuidComponent">
128-
<ROW Component="AI_CustomARPName" Value="{F7C45B12-D093-4D1A-96D3-9D79D73D8DD1}"/>
128+
<ROW Component="AI_CustomARPName" Value="{1EE8167C-7E52-48AD-9053-BD9A132F2918}"/>
129129
<ROW Component="AI_DisableModify" Value="{5AB8A335-BCAB-4C0A-8A69-E2AE7542AB47}"/>
130130
<ROW Component="AI_ExePath" Value="{F7DEDE0C-FDB1-411C-B347-A482CE69721F}"/>
131131
<ROW Component="APPDIR" Value="{6B122BDD-2BF8-41D4-92E0-884DD60467B4}"/>
@@ -366,7 +366,6 @@
366366
<ROW Component="context.cp313win_amd64.pyd" ComponentId="{3F46B25C-6AF9-4049-A39B-9211FFF611B2}" Directory_="filter_Dir" Attributes="256" KeyPath="context.cp313win_amd64.pyd_1" Type="0"/>
367367
<ROW Component="core.cp313win_amd64.pyd" ComponentId="{239884C9-D5A8-4EAC-BB80-98940D72772E}" Directory_="container_Dir" Attributes="256" KeyPath="core.cp313win_amd64.pyd" Type="0"/>
368368
<ROW Component="cs.msg" ComponentId="{CA47D90F-CA31-4D5E-8EB4-2BE8724900E9}" Directory_="msgs_1_Dir" Attributes="0" KeyPath="cs.msg_1" Type="0"/>
369-
<ROW Component="entry_points.txt" ComponentId="{E6C2334A-40CF-4B41-B7E9-ECC421D6A28A}" Directory_="wheel0.45.1.distinfo_Dir" Attributes="0" KeyPath="entry_points.txt_1" Type="0"/>
370369
<ROW Component="ffmpeg.exe" ComponentId="{B7CA4B2A-DE88-42CD-AD38-AC30FD29D506}" Directory_="APPDIR" Attributes="256" KeyPath="ffmpeg.exe"/>
371370
<ROW Component="http.tcl" ComponentId="{E68A9C81-9D27-40F2-88A7-4B2CF7388062}" Directory_="http1.0_Dir" Attributes="0" KeyPath="http.tcl" Type="0"/>
372371
<ROW Component="http2.9.8.tm" ComponentId="{80BA6FC3-9CCC-4A73-8673-F6C6BC2106B5}" Directory_="__2_Dir" Attributes="0" KeyPath="http2.9.8.tm" Type="0"/>
@@ -639,13 +638,6 @@
639638
<ROW File="unicodedata.pyd" Component_="base_library.zip" FileName="UNICOD~1.PYD|unicodedata.pyd" Attributes="0" SourcePath="..\scenedetect\_internal\unicodedata.pyd" SelfReg="false"/>
640639
<ROW File="VCRUNTIME140.dll" Component_="VCRUNTIME140.dll" FileName="VCRUNT~1.DLL|VCRUNTIME140.dll" Attributes="0" SourcePath="..\scenedetect\_internal\VCRUNTIME140.dll" SelfReg="false"/>
641640
<ROW File="VCRUNTIME140_1.dll" Component_="VCRUNTIME140_1.dll" FileName="VCRUNT~2.DLL|VCRUNTIME140_1.dll" Attributes="0" SourcePath="..\scenedetect\_internal\VCRUNTIME140_1.dll" SelfReg="false"/>
642-
<ROW File="entry_points.txt_1" Component_="entry_points.txt" FileName="ENTRY_~1.TXT|entry_points.txt" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\entry_points.txt" SelfReg="false"/>
643-
<ROW File="INSTALLER_2" Component_="entry_points.txt" FileName="INSTAL~1|INSTALLER" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\INSTALLER" SelfReg="false"/>
644-
<ROW File="LICENSE.txt_1" Component_="entry_points.txt" FileName="LICENSE.txt" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\LICENSE.txt" SelfReg="false"/>
645-
<ROW File="METADATA_2" Component_="entry_points.txt" FileName="METADATA" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\METADATA" SelfReg="false"/>
646-
<ROW File="RECORD_2" Component_="entry_points.txt" FileName="RECORD" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\RECORD" SelfReg="false"/>
647-
<ROW File="REQUESTED_2" Component_="entry_points.txt" FileName="REQUES~1|REQUESTED" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\REQUESTED" SelfReg="false"/>
648-
<ROW File="WHEEL_2" Component_="entry_points.txt" FileName="WHEEL" Attributes="0" SourcePath="..\scenedetect\_internal\wheel-0.45.1.dist-info\WHEEL" SelfReg="false"/>
649641
<ROW File="zlib1.dll" Component_="zlib1.dll" FileName="zlib1.dll" Attributes="0" SourcePath="..\scenedetect\_internal\zlib1.dll" SelfReg="false"/>
650642
<ROW File="_asyncio.pyd" Component_="base_library.zip" FileName="_asyncio.pyd" Attributes="0" SourcePath="..\scenedetect\_internal\_asyncio.pyd" SelfReg="false"/>
651643
<ROW File="_bz2.pyd" Component_="base_library.zip" FileName="_bz2.pyd" Attributes="0" SourcePath="..\scenedetect\_internal\_bz2.pyd" SelfReg="false"/>
@@ -1642,7 +1634,7 @@
16421634
<ROW Action="AI_CleanPrePrereq" Condition="AI_BOOTSTRAPPER AND (NOT AI_PrereqsFulfilled)" Sequence="699"/>
16431635
</COMPONENT>
16441636
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
1645-
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="." PackageFileName="PySceneDetect-0.6.6-win64" Languages="en" InstallationType="4" ExtUI="true" UseLargeSchema="true" MsiPackageType="x64"/>
1637+
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="." PackageFileName="PySceneDetect-0.6.7-win64" Languages="en" InstallationType="4" ExtUI="true" UseLargeSchema="true" MsiPackageType="x64"/>
16461638
</COMPONENT>
16471639
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
16481640
<ROW Path="&lt;AI_DICTS&gt;ui.ail"/>
@@ -1897,7 +1889,6 @@
18971889
<ROW Feature_="PySceneDetect" Component_="ucrtbase.dll"/>
18981890
<ROW Feature_="PySceneDetect" Component_="VCRUNTIME140.dll"/>
18991891
<ROW Feature_="PySceneDetect" Component_="VCRUNTIME140_1.dll"/>
1900-
<ROW Feature_="PySceneDetect" Component_="entry_points.txt"/>
19011892
<ROW Feature_="PySceneDetect" Component_="zlib1.dll"/>
19021893
<ROW Feature_="PySceneDetect" Component_="auto.tcl"/>
19031894
<ROW Feature_="PySceneDetect" Component_="ascii.enc"/>

docs/cli.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,7 @@ Options
178178

179179
Default: ``15.0``
180180

181-
.. option:: -d VAL, --min-delta-hsv VAL
182181

183-
[DEPRECATED] Use :option:`-c/--min-content-val <-c>` instead.
184-
185-
Default: ``15.0``
186182

187183
.. option:: -f VAL, --frame-window VAL
188184

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
# PySceneDetect Requirements
33
#
44
av>=9.2
5-
click>=8.0
5+
# click 8.3.0 is excluded as per https://scenedetect.com/issues/521
6+
click~=8.0,<8.3.0
67
numpy
78
opencv-python
89
platformdirs

requirements_headless.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
# PySceneDetect Requirements for Headless Machines
33
#
44
av>=9.2
5-
click>=8.0
5+
# click 8.3.0 is excluded as per https://scenedetect.com/issues/521
6+
click~=8.0,<8.3.0
67
numpy
78
opencv-python-headless
89
platformdirs

scenedetect/_cli/__init__.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -615,16 +615,6 @@ def detect_content_command(
615615
help='Minimum threshold (float) that "content_val" must exceed to trigger a cut.%s'
616616
% (USER_CONFIG.get_help_string("detect-adaptive", "min-content-val")),
617617
)
618-
@click.option(
619-
"--min-delta-hsv",
620-
"-d",
621-
metavar="VAL",
622-
type=click.FLOAT,
623-
default=None,
624-
help="[DEPRECATED] Use -c/--min-content-val instead.%s"
625-
% (USER_CONFIG.get_help_string("detect-adaptive", "min-delta-hsv")),
626-
hidden=True,
627-
)
628618
@click.option(
629619
"--frame-window",
630620
"-f",
@@ -677,7 +667,6 @@ def detect_adaptive_command(
677667
ctx: click.Context,
678668
threshold: ty.Optional[float],
679669
min_content_val: ty.Optional[float],
680-
min_delta_hsv: ty.Optional[float],
681670
frame_window: ty.Optional[int],
682671
weights: ty.Optional[ty.Tuple[float, float, float, float]],
683672
luma_only: bool,
@@ -689,7 +678,6 @@ def detect_adaptive_command(
689678
detector_args = ctx.get_detect_adaptive_params(
690679
threshold=threshold,
691680
min_content_val=min_content_val,
692-
min_delta_hsv=min_delta_hsv,
693681
frame_window=frame_window,
694682
luma_only=luma_only,
695683
min_scene_len=min_scene_len,

0 commit comments

Comments
 (0)