cv-frames 0.0.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.
- cv_frames-0.0.0/.github/workflows/publish.yml +33 -0
- cv_frames-0.0.0/.github/workflows/tests.yml +30 -0
- cv_frames-0.0.0/.gitignore +5 -0
- cv_frames-0.0.0/.pre-commit-config.yaml +39 -0
- cv_frames-0.0.0/PKG-INFO +6 -0
- cv_frames-0.0.0/README.md +1 -0
- cv_frames-0.0.0/cv_frames.egg-info/PKG-INFO +6 -0
- cv_frames-0.0.0/cv_frames.egg-info/SOURCES.txt +15 -0
- cv_frames-0.0.0/cv_frames.egg-info/dependency_links.txt +1 -0
- cv_frames-0.0.0/cv_frames.egg-info/requires.txt +2 -0
- cv_frames-0.0.0/cv_frames.egg-info/top_level.txt +1 -0
- cv_frames-0.0.0/cvframes/__init__.py +0 -0
- cv_frames-0.0.0/cvframes/iterate.py +106 -0
- cv_frames-0.0.0/pyproject.toml +41 -0
- cv_frames-0.0.0/requirements.txt +3 -0
- cv_frames-0.0.0/setup.cfg +4 -0
- cv_frames-0.0.0/tests/test_iterate.py +56 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
deploy:
|
|
9
|
+
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write # Required for OIDC authentication
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v4
|
|
19
|
+
with:
|
|
20
|
+
python-version: '3.x'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install build
|
|
26
|
+
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: python -m build
|
|
29
|
+
|
|
30
|
+
- name: Publish to PyPI
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
32
|
+
with:
|
|
33
|
+
verbose: true
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- "**"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v3
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v4
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.10"
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
pip install -r requirements.txt
|
|
23
|
+
pip install -e .
|
|
24
|
+
|
|
25
|
+
- name: Run the tests
|
|
26
|
+
run: |
|
|
27
|
+
PYTHONFAULTHANDLER=2 pytest --cov=cvframes --cov-report term-missing tests/
|
|
28
|
+
|
|
29
|
+
- name: Run pre-commit checks
|
|
30
|
+
uses: pre-commit/action@v3.0.1
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.0.1
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-added-large-files
|
|
6
|
+
args: ["--maxkb=1000"]
|
|
7
|
+
- id: check-json
|
|
8
|
+
- id: check-docstring-first
|
|
9
|
+
- id: end-of-file-fixer
|
|
10
|
+
- id: trailing-whitespace
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/pycqa/isort
|
|
13
|
+
rev: 5.12.0
|
|
14
|
+
hooks:
|
|
15
|
+
- id: isort
|
|
16
|
+
name: isort (python)
|
|
17
|
+
args: ["--settings-path", "pyproject.toml"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
- repo: https://github.com/psf/black
|
|
21
|
+
rev: 22.3.0
|
|
22
|
+
hooks:
|
|
23
|
+
- id: black
|
|
24
|
+
args: ["--config", "pyproject.toml"]
|
|
25
|
+
|
|
26
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
27
|
+
rev: v0.921
|
|
28
|
+
hooks:
|
|
29
|
+
- id: mypy
|
|
30
|
+
additional_dependencies: ["types-PyYAML"]
|
|
31
|
+
args: ["--config-file", "pyproject.toml"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
- repo: https://github.com/pycqa/flake8
|
|
35
|
+
rev: 4.0.1
|
|
36
|
+
hooks:
|
|
37
|
+
- id: flake8
|
|
38
|
+
# args: ["--config", "pyproject.toml"]
|
|
39
|
+
args: ["--max-line-length=79"]
|
cv_frames-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Read frames from OpenCV like humans
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
.pre-commit-config.yaml
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
requirements.txt
|
|
6
|
+
.github/workflows/publish.yml
|
|
7
|
+
.github/workflows/tests.yml
|
|
8
|
+
cv_frames.egg-info/PKG-INFO
|
|
9
|
+
cv_frames.egg-info/SOURCES.txt
|
|
10
|
+
cv_frames.egg-info/dependency_links.txt
|
|
11
|
+
cv_frames.egg-info/requires.txt
|
|
12
|
+
cv_frames.egg-info/top_level.txt
|
|
13
|
+
cvframes/__init__.py
|
|
14
|
+
cvframes/iterate.py
|
|
15
|
+
tests/test_iterate.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cvframes
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Callable, Generator, Optional, Tuple, TypeVar
|
|
3
|
+
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IOCapture:
|
|
11
|
+
def __init__(self, source: str | Path, oname: str | Path = ""):
|
|
12
|
+
self.icap = cv2.VideoCapture(str(source))
|
|
13
|
+
self.ocap = (
|
|
14
|
+
cv2.VideoWriter(
|
|
15
|
+
str(oname),
|
|
16
|
+
cv2.VideoWriter_fourcc(*"mp4v"),
|
|
17
|
+
self.icap.get(cv2.CAP_PROP_FPS),
|
|
18
|
+
(
|
|
19
|
+
int(self.icap.get(cv2.CAP_PROP_FRAME_WIDTH)),
|
|
20
|
+
int(self.icap.get(cv2.CAP_PROP_FRAME_HEIGHT)),
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
if oname
|
|
24
|
+
else None
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def is_opened(self) -> bool:
|
|
28
|
+
return self.icap.isOpened()
|
|
29
|
+
|
|
30
|
+
def read(self) -> Tuple[bool, np.ndarray]:
|
|
31
|
+
return self.icap.read()
|
|
32
|
+
|
|
33
|
+
def write(self, frame: np.ndarray) -> None:
|
|
34
|
+
if self.ocap is not None:
|
|
35
|
+
self.ocap.write(frame)
|
|
36
|
+
|
|
37
|
+
def release(self) -> None:
|
|
38
|
+
self.icap.release()
|
|
39
|
+
if self.ocap is not None:
|
|
40
|
+
self.ocap.release()
|
|
41
|
+
|
|
42
|
+
def set(self, prop_id: int, value: float) -> None:
|
|
43
|
+
self.icap.set(prop_id, value)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def iterate_generic(
|
|
47
|
+
ipath: Path,
|
|
48
|
+
opath: Optional[Path],
|
|
49
|
+
start_frame: int,
|
|
50
|
+
stop_frame: int,
|
|
51
|
+
process_frames: Callable[[np.ndarray], T],
|
|
52
|
+
) -> Generator[tuple[IOCapture, T], None, None]:
|
|
53
|
+
capture = IOCapture(str(ipath), oname=opath or "")
|
|
54
|
+
capture.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
|
55
|
+
count = start_frame
|
|
56
|
+
|
|
57
|
+
if not capture.is_opened():
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
while True:
|
|
62
|
+
ret, frame = capture.read()
|
|
63
|
+
count += 1
|
|
64
|
+
if not ret:
|
|
65
|
+
break
|
|
66
|
+
if stop_frame > 0 and count >= stop_frame:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
yield capture, process_frames(frame)
|
|
70
|
+
finally:
|
|
71
|
+
capture.release()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def iterate(
|
|
75
|
+
ipath: Path,
|
|
76
|
+
opath: Optional[Path] = None,
|
|
77
|
+
start_frame: int = -1,
|
|
78
|
+
stop_frame: int = -1,
|
|
79
|
+
) -> Generator[tuple[IOCapture, np.ndarray], None, None]:
|
|
80
|
+
return iterate_generic(
|
|
81
|
+
ipath,
|
|
82
|
+
opath,
|
|
83
|
+
start_frame,
|
|
84
|
+
stop_frame,
|
|
85
|
+
lambda frame: frame,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def iterate_sbs(
|
|
90
|
+
ipath: Path,
|
|
91
|
+
opath: Optional[Path] = None,
|
|
92
|
+
start_frame: int = -1,
|
|
93
|
+
stop_frame: int = -1,
|
|
94
|
+
) -> Generator[Tuple[IOCapture, Tuple[np.ndarray, np.ndarray]], None, None]:
|
|
95
|
+
def processor(frame: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
96
|
+
_, width, _ = frame.shape
|
|
97
|
+
mid = width // 2
|
|
98
|
+
return frame[:, :mid, :], frame[:, mid:, :]
|
|
99
|
+
|
|
100
|
+
return iterate_generic(
|
|
101
|
+
ipath,
|
|
102
|
+
opath,
|
|
103
|
+
start_frame,
|
|
104
|
+
stop_frame,
|
|
105
|
+
processor,
|
|
106
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[tool.black]
|
|
2
|
+
line-length = 79
|
|
3
|
+
|
|
4
|
+
[tool.isort]
|
|
5
|
+
profile = "black"
|
|
6
|
+
line_length = 79
|
|
7
|
+
|
|
8
|
+
[tool.mypy]
|
|
9
|
+
python_version = "3.10"
|
|
10
|
+
strict = false
|
|
11
|
+
ignore_missing_imports = true
|
|
12
|
+
|
|
13
|
+
[tool.flake8]
|
|
14
|
+
max-line-length = 79
|
|
15
|
+
ignore = [
|
|
16
|
+
"E203", # Whitespace before ':'
|
|
17
|
+
"W503", # Line break before binary operator
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.pytest.ini_options]
|
|
21
|
+
addopts = "--cov=cvframes --cov-report=term-missing"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["setuptools", "wheel", "setuptools_scm"]
|
|
25
|
+
build-backend = "setuptools.build_meta"
|
|
26
|
+
|
|
27
|
+
[project]
|
|
28
|
+
# NB: Keep it the same as in PyPI
|
|
29
|
+
name = "cv-frames"
|
|
30
|
+
dynamic = ["version"]
|
|
31
|
+
description = "Read frames from OpenCV like humans"
|
|
32
|
+
dependencies = [
|
|
33
|
+
"opencv-python",
|
|
34
|
+
"numpy"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
packages = ["cvframes"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools_scm]
|
|
41
|
+
version_scheme = "post-release" # Uses Git tags for versioning
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from cvframes.iterate import iterate, iterate_sbs
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def video(tmp_path: Path):
|
|
13
|
+
opath = tmp_path / "test_video.mp4"
|
|
14
|
+
ovideo = cv2.VideoWriter(
|
|
15
|
+
str(opath),
|
|
16
|
+
cv2.VideoWriter_fourcc(*"mp4v"),
|
|
17
|
+
30, # FPS
|
|
18
|
+
(640, 480),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
for _ in range(5):
|
|
22
|
+
ovideo.write(np.zeros((480, 640, 3), dtype=np.uint8))
|
|
23
|
+
ovideo.release()
|
|
24
|
+
return opath
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize(
|
|
28
|
+
"opath",
|
|
29
|
+
[
|
|
30
|
+
None,
|
|
31
|
+
Path("output.mp4"),
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
def test_iterate(video: Path, opath: Optional[Path]):
|
|
35
|
+
# sourcery skip: no-loop-in-tests
|
|
36
|
+
for capture, frame in iterate(video, opath=opath):
|
|
37
|
+
capture.write(frame)
|
|
38
|
+
assert frame.shape == (480, 640, 3)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.skip("skipping")
|
|
42
|
+
@pytest.mark.parametrize(
|
|
43
|
+
"opath",
|
|
44
|
+
[
|
|
45
|
+
None,
|
|
46
|
+
Path("output.mp4"),
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_iterate_sbs(video: Path, opath: Optional[Path]):
|
|
50
|
+
# sourcery skip: no-loop-in-tests
|
|
51
|
+
for capture, (lframe, rframe) in iterate_sbs(
|
|
52
|
+
Path("input.mp4"), opath=opath
|
|
53
|
+
):
|
|
54
|
+
capture.write(lframe)
|
|
55
|
+
assert lframe.shape == (480, 320, 3)
|
|
56
|
+
assert rframe.shape == (480, 320, 3)
|