iterframes 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iterframes-0.4.0/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- iterframes-0.4.0/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
- iterframes-0.4.0/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- iterframes-0.4.0/.github/workflows/ci.yaml +225 -0
- iterframes-0.4.0/.gitignore +158 -0
- iterframes-0.4.0/.pre-commit-config.yaml +35 -0
- iterframes-0.4.0/.readthedocs.yaml +21 -0
- iterframes-0.4.0/.release-please-manifest.json +3 -0
- iterframes-0.4.0/AGENTS.md +149 -0
- iterframes-0.4.0/CHANGELOG.md +80 -0
- iterframes-0.4.0/CLAUDE.md +5 -0
- iterframes-0.4.0/Cargo.lock +316 -0
- iterframes-0.4.0/Cargo.toml +31 -0
- iterframes-0.4.0/LICENSE +165 -0
- iterframes-0.4.0/PKG-INFO +106 -0
- iterframes-0.4.0/README.md +82 -0
- iterframes-0.4.0/build.rs +152 -0
- iterframes-0.4.0/docs/development.md +140 -0
- iterframes-0.4.0/docs/index.md +71 -0
- iterframes-0.4.0/docs/reference.md +219 -0
- iterframes-0.4.0/iterframes/__init__.py +88 -0
- iterframes-0.4.0/mkdocs.yml +67 -0
- iterframes-0.4.0/pyproject.toml +71 -0
- iterframes-0.4.0/release-please-config.json +14 -0
- iterframes-0.4.0/scripts/build-ffmpeg.sh +134 -0
- iterframes-0.4.0/src/decoder.rs +183 -0
- iterframes-0.4.0/src/dlpack.rs +142 -0
- iterframes-0.4.0/src/ffmpeg.rs +631 -0
- iterframes-0.4.0/src/lib.rs +370 -0
- iterframes-0.4.0/tests/conftest.py +39 -0
- iterframes-0.4.0/tests/data/video_480x270.mp4 +0 -0
- iterframes-0.4.0/tests/data/video_av1_480x270.mp4 +0 -0
- iterframes-0.4.0/tests/test_benchmark.py +61 -0
- iterframes-0.4.0/tests/test_read.py +232 -0
- iterframes-0.4.0/uv.lock +958 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug Report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: '[BUG] '
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Bug Description
|
|
10
|
+
|
|
11
|
+
A clear and concise description of what the bug is.
|
|
12
|
+
|
|
13
|
+
## Code Example
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import iterframes
|
|
17
|
+
|
|
18
|
+
# Minimal code example that reproduces the issue
|
|
19
|
+
for frame in iterframes.read("video.mp4"):
|
|
20
|
+
...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Error Message
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Full error message and traceback if applicable
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Video
|
|
30
|
+
|
|
31
|
+
The container and codec of the video, as printed by
|
|
32
|
+
`ffprobe -hide_banner video.mp4`. Attach the file if you can share it.
|
|
33
|
+
|
|
34
|
+
## Environment
|
|
35
|
+
|
|
36
|
+
- **OS:** [e.g., macOS 15, Ubuntu 24.04]
|
|
37
|
+
- **Python version:** [e.g., 3.13.2]
|
|
38
|
+
- **iterframes version:** [`iterframes.__version__`]
|
|
39
|
+
- **FFmpeg version:** [`iterframes.FFMPEG_VERSION`]
|
|
40
|
+
- **Installation method:** [wheel from PyPI, from source]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature Request
|
|
3
|
+
about: Suggest a new feature for this project
|
|
4
|
+
title: '[FEATURE] '
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Feature Description
|
|
10
|
+
|
|
11
|
+
A clear and concise description of the feature you'd like to see added.
|
|
12
|
+
|
|
13
|
+
## Use Case
|
|
14
|
+
|
|
15
|
+
Describe the use case or problem this feature would solve.
|
|
16
|
+
|
|
17
|
+
## Example Usage
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import iterframes
|
|
21
|
+
|
|
22
|
+
# How you would like to use this feature
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Alternatives Considered
|
|
26
|
+
|
|
27
|
+
Have you considered any alternative solutions or features?
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
<!-- What does this change and why? Link related issues, e.g. "Closes #123". -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `cargo fmt` and `cargo clippy --all-targets -- -D warnings` pass
|
|
8
|
+
- [ ] `uv run --no-sync pre-commit run -a` passes
|
|
9
|
+
- [ ] Tests added or updated, and `uv run --no-sync pytest` passes
|
|
10
|
+
- [ ] Docs and `CHANGELOG.md` updated
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Wheel build matrix adapted from `maturin generate-ci github`. Every wheel
|
|
2
|
+
# links a static FFmpeg, which build.rs builds with scripts/build-ffmpeg.sh
|
|
3
|
+
# into build/ffmpeg, and is abi3, so a single wheel per platform covers
|
|
4
|
+
# every CPython from 3.11 on. Edit by hand.
|
|
5
|
+
name: CI
|
|
6
|
+
|
|
7
|
+
# Releases come from release-please, in this workflow: a push to main
|
|
8
|
+
# updates its release pull request, and merging that pull request creates
|
|
9
|
+
# the tag and a draft GitHub release, then the jobs below attach the
|
|
10
|
+
# wheels, publish them to PyPI, and publish the release.
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches:
|
|
14
|
+
- main
|
|
15
|
+
pull_request:
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
release-please:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
if: ${{ github.event_name == 'push' }}
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
pull-requests: write
|
|
28
|
+
outputs:
|
|
29
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
30
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
31
|
+
version: ${{ steps.release.outputs.version }}
|
|
32
|
+
steps:
|
|
33
|
+
# Reads release-please-config.json and .release-please-manifest.json.
|
|
34
|
+
# With the default GITHUB_TOKEN, its tag and pull request start no
|
|
35
|
+
# other workflow, which is why the release jobs live here.
|
|
36
|
+
- uses: googleapis/release-please-action@v4
|
|
37
|
+
id: release
|
|
38
|
+
|
|
39
|
+
lint:
|
|
40
|
+
runs-on: ubuntu-24.04
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v7
|
|
43
|
+
- name: Install uv
|
|
44
|
+
uses: astral-sh/setup-uv@v10.1.0
|
|
45
|
+
- name: Install build tools
|
|
46
|
+
# Clippy runs build.rs, which builds FFmpeg and generates its
|
|
47
|
+
# bindings with libclang.
|
|
48
|
+
run: |
|
|
49
|
+
sudo apt-get update
|
|
50
|
+
sudo apt-get install -y --no-install-recommends \
|
|
51
|
+
libclang-dev nasm pkg-config
|
|
52
|
+
- name: Cache FFmpeg
|
|
53
|
+
uses: actions/cache@v6
|
|
54
|
+
with:
|
|
55
|
+
path: build/ffmpeg
|
|
56
|
+
key: ffmpeg-lint-${{ hashFiles('scripts/build-ffmpeg.sh') }}
|
|
57
|
+
- name: Check Rust formatting
|
|
58
|
+
run: cargo fmt --check
|
|
59
|
+
- name: Lint Rust
|
|
60
|
+
run: cargo clippy --all-targets -- -D warnings
|
|
61
|
+
- name: Lint Python
|
|
62
|
+
run: |
|
|
63
|
+
uvx ruff@0.16.8 check .
|
|
64
|
+
uvx ruff@0.16.8 format --check .
|
|
65
|
+
|
|
66
|
+
linux:
|
|
67
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
68
|
+
strategy:
|
|
69
|
+
matrix:
|
|
70
|
+
platform:
|
|
71
|
+
# Native runners for each architecture: FFmpeg is compiled in the
|
|
72
|
+
# manylinux container, so there is no cross-compilation to set up.
|
|
73
|
+
- runner: ubuntu-24.04
|
|
74
|
+
target: x86_64
|
|
75
|
+
- runner: ubuntu-24.04-arm
|
|
76
|
+
target: aarch64
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v7
|
|
79
|
+
- name: Cache FFmpeg
|
|
80
|
+
uses: actions/cache@v6
|
|
81
|
+
with:
|
|
82
|
+
path: build/ffmpeg
|
|
83
|
+
key: ffmpeg-linux-${{ matrix.platform.target }}-${{ hashFiles('scripts/build-ffmpeg.sh') }}
|
|
84
|
+
- name: Build wheels
|
|
85
|
+
uses: PyO3/maturin-action@v1
|
|
86
|
+
with:
|
|
87
|
+
target: ${{ matrix.platform.target }}
|
|
88
|
+
args: --release --out dist
|
|
89
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
90
|
+
manylinux: 2_28
|
|
91
|
+
# libclang, for the bindings build.rs generates.
|
|
92
|
+
before-script-linux: dnf install -y clang-devel
|
|
93
|
+
- name: Upload wheels
|
|
94
|
+
uses: actions/upload-artifact@v7
|
|
95
|
+
with:
|
|
96
|
+
name: wheels-linux-${{ matrix.platform.target }}
|
|
97
|
+
path: dist
|
|
98
|
+
- name: Install uv
|
|
99
|
+
uses: astral-sh/setup-uv@v10.1.0
|
|
100
|
+
- name: Run tests
|
|
101
|
+
shell: bash
|
|
102
|
+
run: |
|
|
103
|
+
# The oldest and newest supported Pythons load the same abi3 wheel.
|
|
104
|
+
for python in 3.11 3.14; do
|
|
105
|
+
uv sync --frozen --no-install-project --python $python
|
|
106
|
+
uv pip install --reinstall-package iterframes dist/*.whl
|
|
107
|
+
uv run --no-sync pytest
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
macos:
|
|
111
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
112
|
+
strategy:
|
|
113
|
+
matrix:
|
|
114
|
+
platform:
|
|
115
|
+
# Apple silicon only: Apple is phasing out Intel Macs.
|
|
116
|
+
- runner: macos-15
|
|
117
|
+
target: aarch64
|
|
118
|
+
env:
|
|
119
|
+
# Read by both the FFmpeg build and maturin, which tags the wheel.
|
|
120
|
+
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
|
121
|
+
steps:
|
|
122
|
+
- uses: actions/checkout@v7
|
|
123
|
+
- name: Cache FFmpeg
|
|
124
|
+
uses: actions/cache@v6
|
|
125
|
+
with:
|
|
126
|
+
path: build/ffmpeg
|
|
127
|
+
key: ffmpeg-macos-${{ matrix.platform.target }}-${{ hashFiles('scripts/build-ffmpeg.sh') }}
|
|
128
|
+
- name: Install pkg-config
|
|
129
|
+
run: command -v pkg-config || brew install pkgconf
|
|
130
|
+
- name: Build wheels
|
|
131
|
+
uses: PyO3/maturin-action@v1
|
|
132
|
+
with:
|
|
133
|
+
target: ${{ matrix.platform.target }}
|
|
134
|
+
args: --release --out dist
|
|
135
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
136
|
+
- name: Upload wheels
|
|
137
|
+
uses: actions/upload-artifact@v7
|
|
138
|
+
with:
|
|
139
|
+
name: wheels-macos-${{ matrix.platform.target }}
|
|
140
|
+
path: dist
|
|
141
|
+
- name: Install uv
|
|
142
|
+
uses: astral-sh/setup-uv@v10.1.0
|
|
143
|
+
- name: Run tests
|
|
144
|
+
shell: bash
|
|
145
|
+
run: |
|
|
146
|
+
uv sync --frozen --no-install-project
|
|
147
|
+
uv pip install --reinstall-package iterframes dist/*.whl
|
|
148
|
+
uv run --no-sync pytest
|
|
149
|
+
|
|
150
|
+
sdist:
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
steps:
|
|
153
|
+
- uses: actions/checkout@v7
|
|
154
|
+
- name: Build sdist
|
|
155
|
+
uses: PyO3/maturin-action@v1
|
|
156
|
+
with:
|
|
157
|
+
command: sdist
|
|
158
|
+
args: --out dist
|
|
159
|
+
- name: Upload sdist
|
|
160
|
+
uses: actions/upload-artifact@v7
|
|
161
|
+
with:
|
|
162
|
+
name: wheels-sdist
|
|
163
|
+
path: dist
|
|
164
|
+
|
|
165
|
+
release:
|
|
166
|
+
name: Attach to the GitHub release
|
|
167
|
+
runs-on: ubuntu-latest
|
|
168
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
169
|
+
needs: [release-please, lint, linux, macos, sdist]
|
|
170
|
+
permissions:
|
|
171
|
+
# Used to sign the attestations
|
|
172
|
+
id-token: write
|
|
173
|
+
# Used to upload the files to the draft release
|
|
174
|
+
contents: write
|
|
175
|
+
# Used to generate artifact attestation
|
|
176
|
+
attestations: write
|
|
177
|
+
steps:
|
|
178
|
+
- uses: actions/download-artifact@v8
|
|
179
|
+
- name: Generate artifact attestation
|
|
180
|
+
uses: actions/attest@v4
|
|
181
|
+
with:
|
|
182
|
+
subject-path: 'wheels-*/*'
|
|
183
|
+
- name: Upload to the draft release
|
|
184
|
+
env:
|
|
185
|
+
GH_TOKEN: ${{ github.token }}
|
|
186
|
+
run: >
|
|
187
|
+
gh release upload ${{ needs.release-please.outputs.tag_name }}
|
|
188
|
+
wheels-*/* --clobber --repo ${{ github.repository }}
|
|
189
|
+
|
|
190
|
+
publish:
|
|
191
|
+
name: Publish to PyPI
|
|
192
|
+
runs-on: ubuntu-latest
|
|
193
|
+
needs: [release-please, release]
|
|
194
|
+
# Naming an environment records the run under Deployments, where the
|
|
195
|
+
# release can also be gated behind a review.
|
|
196
|
+
environment:
|
|
197
|
+
name: pypi
|
|
198
|
+
url: https://pypi.org/project/iterframes/${{ needs.release-please.outputs.version }}
|
|
199
|
+
permissions:
|
|
200
|
+
id-token: write
|
|
201
|
+
steps:
|
|
202
|
+
- uses: actions/download-artifact@v8
|
|
203
|
+
- name: Install uv
|
|
204
|
+
uses: astral-sh/setup-uv@v10.1.0
|
|
205
|
+
- name: Publish to PyPI
|
|
206
|
+
# Trusted publishing: PyPI mints a short-lived token from the job's
|
|
207
|
+
# OIDC identity, so no API token is stored. "always" fails loudly if
|
|
208
|
+
# that identity is missing instead of falling back.
|
|
209
|
+
run: uv publish --trusted-publishing always 'wheels-*/*'
|
|
210
|
+
|
|
211
|
+
publish-release:
|
|
212
|
+
name: Publish the GitHub release
|
|
213
|
+
runs-on: ubuntu-latest
|
|
214
|
+
# Last, so that the release only shows once its files are attached and
|
|
215
|
+
# on PyPI.
|
|
216
|
+
needs: [release-please, publish]
|
|
217
|
+
permissions:
|
|
218
|
+
contents: write
|
|
219
|
+
steps:
|
|
220
|
+
- name: Publish the draft release
|
|
221
|
+
env:
|
|
222
|
+
GH_TOKEN: ${{ github.token }}
|
|
223
|
+
run: >
|
|
224
|
+
gh release edit ${{ needs.release-please.outputs.tag_name }}
|
|
225
|
+
--draft=false --repo ${{ github.repository }}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
## IDE ##
|
|
2
|
+
|
|
3
|
+
.idea/
|
|
4
|
+
.vscode/
|
|
5
|
+
|
|
6
|
+
## Rust related files ##
|
|
7
|
+
|
|
8
|
+
# Generated by Cargo
|
|
9
|
+
debug/
|
|
10
|
+
target/
|
|
11
|
+
|
|
12
|
+
# These are backup files generated by rustfmt
|
|
13
|
+
**/*.rs.bk
|
|
14
|
+
|
|
15
|
+
## Python related files ##
|
|
16
|
+
|
|
17
|
+
# Byte-compiled / optimized / DLL files
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
*$py.class
|
|
21
|
+
|
|
22
|
+
# C extensions
|
|
23
|
+
*.so
|
|
24
|
+
|
|
25
|
+
# Distribution / packaging
|
|
26
|
+
.Python
|
|
27
|
+
build/
|
|
28
|
+
develop-eggs/
|
|
29
|
+
dist/
|
|
30
|
+
downloads/
|
|
31
|
+
eggs/
|
|
32
|
+
.eggs/
|
|
33
|
+
lib/
|
|
34
|
+
lib64/
|
|
35
|
+
parts/
|
|
36
|
+
sdist/
|
|
37
|
+
var/
|
|
38
|
+
wheels/
|
|
39
|
+
wheelhouse/
|
|
40
|
+
share/python-wheels/
|
|
41
|
+
*.egg-info/
|
|
42
|
+
.installed.cfg
|
|
43
|
+
*.egg
|
|
44
|
+
MANIFEST
|
|
45
|
+
|
|
46
|
+
# PyInstaller
|
|
47
|
+
# Usually these files are written by a python script from a template
|
|
48
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
49
|
+
*.manifest
|
|
50
|
+
*.spec
|
|
51
|
+
|
|
52
|
+
# Installer logs
|
|
53
|
+
pip-log.txt
|
|
54
|
+
pip-delete-this-directory.txt
|
|
55
|
+
|
|
56
|
+
# Unit test / coverage reports
|
|
57
|
+
htmlcov/
|
|
58
|
+
.tox/
|
|
59
|
+
.nox/
|
|
60
|
+
.coverage
|
|
61
|
+
.coverage.*
|
|
62
|
+
.cache
|
|
63
|
+
nosetests.xml
|
|
64
|
+
coverage.xml
|
|
65
|
+
*.cover
|
|
66
|
+
*.py,cover
|
|
67
|
+
.hypothesis/
|
|
68
|
+
.pytest_cache/
|
|
69
|
+
cover/
|
|
70
|
+
|
|
71
|
+
# Translations
|
|
72
|
+
*.mo
|
|
73
|
+
*.pot
|
|
74
|
+
|
|
75
|
+
# Django stuff:
|
|
76
|
+
*.log
|
|
77
|
+
local_settings.py
|
|
78
|
+
db.sqlite3
|
|
79
|
+
db.sqlite3-journal
|
|
80
|
+
|
|
81
|
+
# Flask stuff:
|
|
82
|
+
instance/
|
|
83
|
+
.webassets-cache
|
|
84
|
+
|
|
85
|
+
# Scrapy stuff:
|
|
86
|
+
.scrapy
|
|
87
|
+
|
|
88
|
+
# Sphinx documentation
|
|
89
|
+
docs/_build/
|
|
90
|
+
|
|
91
|
+
# PyBuilder
|
|
92
|
+
.pybuilder/
|
|
93
|
+
target/
|
|
94
|
+
|
|
95
|
+
# Jupyter Notebook
|
|
96
|
+
.ipynb_checkpoints
|
|
97
|
+
|
|
98
|
+
# IPython
|
|
99
|
+
profile_default/
|
|
100
|
+
ipython_config.py
|
|
101
|
+
|
|
102
|
+
# pyenv
|
|
103
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
104
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
105
|
+
# .python-version
|
|
106
|
+
|
|
107
|
+
# pipenv
|
|
108
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
109
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
110
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
111
|
+
# install all needed dependencies.
|
|
112
|
+
#Pipfile.lock
|
|
113
|
+
|
|
114
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
115
|
+
__pypackages__/
|
|
116
|
+
|
|
117
|
+
# Celery stuff
|
|
118
|
+
celerybeat-schedule
|
|
119
|
+
celerybeat.pid
|
|
120
|
+
|
|
121
|
+
# SageMath parsed files
|
|
122
|
+
*.sage.py
|
|
123
|
+
|
|
124
|
+
# Environments
|
|
125
|
+
.env
|
|
126
|
+
.venv
|
|
127
|
+
env/
|
|
128
|
+
venv/
|
|
129
|
+
ENV/
|
|
130
|
+
env.bak/
|
|
131
|
+
venv.bak/
|
|
132
|
+
|
|
133
|
+
# Spyder project settings
|
|
134
|
+
.spyderproject
|
|
135
|
+
.spyproject
|
|
136
|
+
|
|
137
|
+
# Rope project settings
|
|
138
|
+
.ropeproject
|
|
139
|
+
|
|
140
|
+
# mkdocs documentation
|
|
141
|
+
/site
|
|
142
|
+
|
|
143
|
+
# mypy
|
|
144
|
+
.mypy_cache/
|
|
145
|
+
.dmypy.json
|
|
146
|
+
dmypy.json
|
|
147
|
+
|
|
148
|
+
# Pyre type checker
|
|
149
|
+
.pyre/
|
|
150
|
+
|
|
151
|
+
# pytype static type analyzer
|
|
152
|
+
.pytype/
|
|
153
|
+
|
|
154
|
+
# Cython debug symbols
|
|
155
|
+
cython_debug/
|
|
156
|
+
|
|
157
|
+
# MacOS files
|
|
158
|
+
.DS_Store
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
# mkdocs.yml uses !!python/name tags for the Material emoji extension.
|
|
9
|
+
args: [--unsafe]
|
|
10
|
+
- id: check-toml
|
|
11
|
+
- id: check-added-large-files
|
|
12
|
+
- id: mixed-line-ending
|
|
13
|
+
args: [ --fix=lf ]
|
|
14
|
+
exclude: "make.bat"
|
|
15
|
+
# Run the local toolchain: the third-party Rust hook mirrors are stale.
|
|
16
|
+
- repo: local
|
|
17
|
+
hooks:
|
|
18
|
+
- id: cargo-fmt
|
|
19
|
+
name: cargo fmt
|
|
20
|
+
entry: cargo fmt --
|
|
21
|
+
language: system
|
|
22
|
+
types: [rust]
|
|
23
|
+
pass_filenames: false
|
|
24
|
+
- id: cargo-clippy
|
|
25
|
+
name: cargo clippy
|
|
26
|
+
entry: cargo clippy --all-targets -- -D warnings
|
|
27
|
+
language: system
|
|
28
|
+
types: [rust]
|
|
29
|
+
pass_filenames: false
|
|
30
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
31
|
+
rev: v0.16.8
|
|
32
|
+
hooks:
|
|
33
|
+
- id: ruff-format
|
|
34
|
+
- id: ruff-check
|
|
35
|
+
args: [--fix]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
os: ubuntu-24.04
|
|
5
|
+
tools:
|
|
6
|
+
python: "3.13"
|
|
7
|
+
jobs:
|
|
8
|
+
# The docs are plain Markdown, so only the docs group is installed and
|
|
9
|
+
# the extension, which would need FFmpeg, is not compiled.
|
|
10
|
+
pre_create_environment:
|
|
11
|
+
- asdf plugin add uv
|
|
12
|
+
- asdf install uv latest
|
|
13
|
+
- asdf global uv latest
|
|
14
|
+
create_environment:
|
|
15
|
+
- uv venv "${READTHEDOCS_VIRTUALENV_PATH}"
|
|
16
|
+
install:
|
|
17
|
+
- UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --frozen --only-group docs
|
|
18
|
+
|
|
19
|
+
mkdocs:
|
|
20
|
+
configuration: mkdocs.yml
|
|
21
|
+
fail_on_warning: false
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Working notes for coding agents (and humans) touching this repository.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
`iterframes` is a Python extension module written in Rust with
|
|
8
|
+
[PyO3](https://pyo3.rs/) and built by [maturin](https://www.maturin.rs/).
|
|
9
|
+
It decodes videos with a static FFmpeg, through bindings that `build.rs`
|
|
10
|
+
generates and `src/ffmpeg.rs` wraps, and yields
|
|
11
|
+
the frames as NumPy arrays of RGB pixels.
|
|
12
|
+
|
|
13
|
+
The point of the project is a plain Python loop over the frames in which
|
|
14
|
+
decoding overlaps with expensive work on each frame, such as model
|
|
15
|
+
inference: while the caller processes one frame, a background thread
|
|
16
|
+
decodes the next ones. Keep that true, and say so in the docs.
|
|
17
|
+
|
|
18
|
+
The package uses maturin's mixed layout: the compiled module is installed
|
|
19
|
+
as `iterframes.iterframes`, and `iterframes/__init__.py` wraps its
|
|
20
|
+
`FrameReader` into `read` and `read_all`.
|
|
21
|
+
|
|
22
|
+
## How decoding works
|
|
23
|
+
|
|
24
|
+
- `FrameReader::new` spawns a thread (`src/decoder.rs`) that demuxes,
|
|
25
|
+
decodes, and converts each frame to RGB24 with swscale, and sends it
|
|
26
|
+
through a bounded crossbeam channel of `prefetch_frames` slots.
|
|
27
|
+
- RGB frames are allocated with an alignment of 1, so their rows have no
|
|
28
|
+
padding and Python can use them as contiguous arrays.
|
|
29
|
+
- Every call into FFmpeg, and so almost all the `unsafe` code, lives in
|
|
30
|
+
`src/ffmpeg.rs`, behind small RAII types. Keep it that way: the rest of
|
|
31
|
+
the crate stays safe.
|
|
32
|
+
- `__next__` waits on the channel with the GIL released (`py.detach`) and
|
|
33
|
+
wraps the frame in a `Frame`, whose buffer protocol hands the pixels to
|
|
34
|
+
NumPy without a copy. The buffer protocol needs `abi3-py311`.
|
|
35
|
+
- The decoder thread never takes the GIL, so it keeps decoding while
|
|
36
|
+
Python code holds it. Never attach to Python there (no `Python::attach`,
|
|
37
|
+
no Python objects in `decoder.rs`); `test_benchmark.py` checks the
|
|
38
|
+
overlap.
|
|
39
|
+
- Errors travel through the channel and become Python exceptions in
|
|
40
|
+
`impl From<Error> for PyErr`. A closed channel means the end of the video.
|
|
41
|
+
- Dropping the reader closes the channel; the thread notices on its next
|
|
42
|
+
send and returns. Never `unwrap` a send.
|
|
43
|
+
|
|
44
|
+
## FFmpeg
|
|
45
|
+
|
|
46
|
+
- Every build, local or CI, links the static FFmpeg that `build.rs`
|
|
47
|
+
builds with `scripts/build-ffmpeg.sh` into `build/ffmpeg` (or
|
|
48
|
+
`$ITERFRAMES_FFMPEG_DIR`) the first time, under a file lock, logging to
|
|
49
|
+
`build/ffmpeg.log`. The script is a no-op when `VERSION` in that
|
|
50
|
+
directory matches what it would build. There is no system FFmpeg mode.
|
|
51
|
+
- `build.rs` links the libraries through `pkg-config --static` and runs
|
|
52
|
+
bindgen on the headers, keeping only `av*_`/`sws_` items. Enums are
|
|
53
|
+
newtype structs: use `.0` for the raw value. Function-like macros such
|
|
54
|
+
as `AVERROR` are written by hand in the `sys` module of `src/ffmpeg.rs`.
|
|
55
|
+
- The build disables autodetection, so the wheel depends on libc and
|
|
56
|
+
system frameworks only; dav1d is added for AV1.
|
|
57
|
+
- Keep the build LGPL: never pass `--enable-gpl` or `--enable-nonfree`.
|
|
58
|
+
- Hardware decoding (`device="mps"` / `"cuda"`, PyTorch's names, mapped
|
|
59
|
+
to FFmpeg's in `hardware_devices` in `src/lib.rs`): VideoToolbox on
|
|
60
|
+
macOS; on Linux the `*_cuvid`
|
|
61
|
+
decoders, which load the NVIDIA driver with dlopen and resize on the
|
|
62
|
+
GPU. Both add no library to the wheel. The macOS build needs clang's
|
|
63
|
+
compiler-rt for `@available`, which `build.rs` links.
|
|
64
|
+
- NVDEC has never run on a GPU in this project: CI has none, and the
|
|
65
|
+
`cuda` tests skip when the device does not open. The same goes for
|
|
66
|
+
`on_device=True` (`CudaFrame`, `Plane`, `src/dlpack.rs`), which waits on
|
|
67
|
+
cuvid's copy with CUDA driver calls found through dlopen
|
|
68
|
+
(`ffmpeg::cuda`).
|
|
69
|
+
|
|
70
|
+
## Layout
|
|
71
|
+
|
|
72
|
+
| Path | Contents |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `src/lib.rs` | PyO3 module: `Frame`, `FrameReader`, error mapping, module init |
|
|
75
|
+
| `src/decoder.rs` | Decoding thread |
|
|
76
|
+
| `src/ffmpeg.rs` | Safe wrappers over the FFmpeg calls the crate needs |
|
|
77
|
+
| `src/dlpack.rs` | DLPack capsules for the planes of `CudaFrame` |
|
|
78
|
+
| `iterframes/__init__.py` | `read`, `read_all` |
|
|
79
|
+
| `build.rs` | Builds and links FFmpeg, generates its bindings |
|
|
80
|
+
| `scripts/build-ffmpeg.sh` | Static FFmpeg and dav1d, run by `build.rs` |
|
|
81
|
+
| `tests/` | pytest suite; frames are compared with PyAV |
|
|
82
|
+
| `docs/` | MkDocs site published on Read the Docs |
|
|
83
|
+
|
|
84
|
+
## Environment
|
|
85
|
+
|
|
86
|
+
Requires [uv](https://docs.astral.sh/uv/), a Rust toolchain, and what
|
|
87
|
+
FFmpeg needs to build: a C compiler, `make`, `curl`, `python3`,
|
|
88
|
+
`pkg-config`, libclang (see `docs/development.md`).
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv venv -p 3.14 # once
|
|
92
|
+
uv sync --frozen # dev dependencies
|
|
93
|
+
uv run maturin develop --uv # build FFmpeg (first time) and the extension
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**`uv run` re-syncs the project by default and overwrites the module that
|
|
97
|
+
`maturin develop` just built.** Always run tests and scripts as:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv run --no-sync pytest
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Checks to run before proposing a change
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cargo fmt
|
|
107
|
+
cargo clippy --all-targets -- -D warnings
|
|
108
|
+
uv run maturin develop --uv
|
|
109
|
+
uv run --no-sync pytest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
CI runs the same four, plus `ruff check .` and `ruff format --check .` for
|
|
113
|
+
the Python files. `pre-commit run -a` covers the formatters and linters
|
|
114
|
+
locally.
|
|
115
|
+
|
|
116
|
+
## Docs
|
|
117
|
+
|
|
118
|
+
Three pages: `index.md` (overview), `reference.md` (API and errors),
|
|
119
|
+
`development.md`. Every example must match the behavior of a freshly
|
|
120
|
+
built module; check them instead of writing them from memory. Keep the
|
|
121
|
+
text short, in American English, with no performance claims that have not
|
|
122
|
+
been measured.
|
|
123
|
+
|
|
124
|
+
## Conventions
|
|
125
|
+
|
|
126
|
+
- Commits follow [Conventional Commits](https://www.conventionalcommits.org/),
|
|
127
|
+
which release-please turns into versions and changelog entries.
|
|
128
|
+
- Rust: `cargo fmt` defaults, no `unwrap()` on anything reachable from
|
|
129
|
+
Python input (a panic surfaces as `PanicException`).
|
|
130
|
+
- Python: ruff with a 79-column limit.
|
|
131
|
+
- Comments explain why, not what.
|
|
132
|
+
|
|
133
|
+
## Release
|
|
134
|
+
|
|
135
|
+
Version lives in `Cargo.toml` and is re-exported as
|
|
136
|
+
`iterframes.__version__`; `pyproject.toml` takes it from there. Never bump
|
|
137
|
+
it, write `CHANGELOG.md` entries, or push tags by hand: release-please
|
|
138
|
+
does, from the Conventional Commits, in the `release-please` job of
|
|
139
|
+
`.github/workflows/ci.yaml` (see "Releasing" in `docs/development.md`).
|
|
140
|
+
Tags are `vX.Y.Z`; before 1.0 a breaking change bumps the minor version.
|
|
141
|
+
|
|
142
|
+
Merging the release pull request creates the tag and a draft GitHub
|
|
143
|
+
release; the same run attaches the wheels (`release` job), uploads them
|
|
144
|
+
to PyPI (`publish`), and publishes the release (`publish-release`). The
|
|
145
|
+
jobs share one workflow because a tag pushed with the default
|
|
146
|
+
`GITHUB_TOKEN` starts no other workflow. The upload uses PyPI trusted
|
|
147
|
+
publishing, bound to this workflow file and the `pypi` environment name:
|
|
148
|
+
renaming either breaks publishing until the publisher is updated on
|
|
149
|
+
PyPI.
|