Skip to content

Commit ac53924

Browse files
authored
Merge pull request #19 from LukasBommes/cibuildwheel
Cibuildwheel
2 parents 94a79e0 + 50671a6 commit ac53924

21 files changed

+314
-160
lines changed

.github/workflows/build.yml

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Build
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build_docker:
7+
name: Build Docker image
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v3
13+
14+
- name: Set up QEMU
15+
uses: docker/setup-qemu-action@v2
16+
17+
- name: Set up Docker Buildx
18+
uses: docker/setup-buildx-action@v2
19+
20+
- name: Login to DockerHub
21+
uses: docker/login-action@v2
22+
with:
23+
username: ${{ secrets.DOCKERHUB_USERNAME }}
24+
password: ${{ secrets.DOCKERHUB_TOKEN }}
25+
26+
- name: Build and push
27+
uses: docker/build-push-action@v3
28+
with:
29+
context: .
30+
push: true
31+
tags: lubo1994/mv-extractor:dev
32+
cache-from: type=registry,ref=lubo1994/mv-extractor:buildcache
33+
cache-to: type=registry,ref=lubo1994/mv-extractor:buildcache,mode=max
34+
35+
build_wheels:
36+
name: Build wheels for cp${{ matrix.python }}-${{ matrix.platform_id }}
37+
runs-on: ${{ matrix.os }}
38+
needs: build_docker
39+
strategy:
40+
# Ensure that a wheel builder finishes even if another fails
41+
fail-fast: false
42+
matrix:
43+
include:
44+
- os: ubuntu-latest
45+
python: 38
46+
bitness: 64
47+
platform_id: manylinux_x86_64
48+
manylinux_image: lubo1994/mv-extractor:dev
49+
- os: ubuntu-latest
50+
python: 39
51+
bitness: 64
52+
platform_id: manylinux_x86_64
53+
manylinux_image: lubo1994/mv-extractor:dev
54+
- os: ubuntu-latest
55+
python: 310
56+
bitness: 64
57+
platform_id: manylinux_x86_64
58+
manylinux_image: lubo1994/mv-extractor:dev
59+
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v3
63+
64+
- name: Build wheels
65+
uses: pypa/[email protected]
66+
env:
67+
CIBW_PLATFORM: linux
68+
CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }}
69+
# Disable building PyPy wheels on all platforms
70+
CIBW_SKIP: pp*
71+
CIBW_ARCHS: x86_64
72+
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux_image }}
73+
#CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.manylinux_image }}
74+
CIBW_BUILD_FRONTEND: build
75+
CIBW_TEST_COMMAND: VIDEO_URL={project}/vid.mp4 python3 {project}/tests/tests.py
76+
CIBW_BUILD_VERBOSITY: 1
77+
78+
- uses: actions/upload-artifact@v3
79+
with:
80+
path: ./wheelhouse/*.whl

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/build/
22
/dist/
33
/wheelhouse/
4-
/*.egg-info
5-
/*.egg
4+
*.egg-info
5+
*.egg
66
.eggs
77
__pycache__/
88
/venv3.*/

Dockerfile

+5-2
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,12 @@ COPY src /home/video_cap/src/
7777

7878
# Install Python package
7979
COPY vid.mp4 /home/video_cap
80-
RUN python3.10 -m pip install --upgrade pip build twine && \
80+
RUN python3.10 -m pip install --upgrade pip build && \
8181
python3.10 -m pip install 'pkgconfig>=1.5.1' 'numpy>=1.17.0'
8282

83-
RUN python3.10 setup.py install
83+
RUN python3.10 -m pip install .
84+
85+
# that is where the "extract_mvs" script is located
86+
ENV PATH="$PATH:/opt/_internal/cpython-3.10.2/bin"
8487

8588
CMD ["sh", "-c", "tail -f /dev/null"]

README.md

+50-30
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ A usage example can be found in `extract_mvs.py`.
2828

2929
### Recent Changes
3030

31+
- Provided PyPI package
3132
- Added unittests in `tests/tests.py`
32-
- Updated Python 3.10, and OpenCV 4.5.5
33+
- Updated for compatibility with Python >3.8
3334
- Provided a script to wrap Docker run command
3435
- Updated demo script with command line arguments for extraction and storing of motion vectors
3536
- Changed Docker image to manylinux_2_24_x86_64 to prepare for building wheels
@@ -38,77 +39,96 @@ A usage example can be found in `extract_mvs.py`.
3839

3940
The mv-extractor seems to be quite popular and I want to improve it. However, I do not have the time and resources to do this alone. Hence, I gladly welcome any community contributions.
4041

41-
One improvement that is on my mind, is the packaging and distribution of mv-extractor via PyPI. This requires creating a source distribution and building binary wheels for different target architectures and Python versions. Ideally, the build would be automated with a CI pipeline, e.g., GitHub Actions. It would be awesome, if someone is interested in helping me and getting this to work. In this case, please just open an issue and we can discuss the details.
42-
4342

4443
## Quickstart
4544

46-
### Step 1: Install Prerequisites
47-
48-
Install [Docker](https://docs.docker.com/).
45+
### Step 1: Install
4946

50-
### Step 2: Clone Source Code
51-
52-
Change into the desired installation directory on your machine and clone the source code
47+
You can install the motion vector extractor via pip
5348
```
54-
git clone https://github.com/LukasBommes/mv-extractor.git mv_extractor
49+
pip install --upgrade pip
50+
pip install motion-vector-extractor
5551
```
52+
Note, that we currently provide the package only for x86-64 linux, such as Ubuntu or Debian, and Python 3.8, 3.9, and 3.10. If you are on a different platform, please use the Docker image as described [below](#installation-via-docker).
5653

57-
### Step 3: Extract Motion Vectors
54+
### Step 2: Extract Motion Vectors
5855

59-
Change into the `mv_extractor` directory and run the extraction script
56+
Download the example video `vid.mp4` from the repo and place it somewhere. To extract the motion vectors, open a terminal at the same location and run
6057
```
61-
sudo ./run.sh python3.10 extract_mvs.py
58+
extract_mvs vid.mp4 --preview --verbose
6259
```
63-
This pulls a prebuild Docker image and runs the mv-extractor within this image.
6460

65-
The extraction script provides several command line options, e.g., to store extracted motion vectors to disk, and to enable/disable graphical output. To see all command line options type
61+
The extraction script provides command line options to store extracted motion vectors to disk, and to enable/disable graphical output. For all options type
6662
```
67-
sudo ./run.sh python3.10 extract_mvs.py -h
63+
extract_mvs -h
6864
```
69-
For example, if you want to store extracted frames and motion vectors to disk, you can do so by running
65+
For example, to store extracted frames and motion vectors to disk without showing graphical output run
7066
```
71-
sudo ./run.sh python3.10 extract_mvs.py --dump
67+
extract_mvs vid.mp4 --dump
7268
```
73-
You can also open another video stream by specifying its file path or url, e.g.,
74-
```
75-
sudo ./run.sh python3.10 extract_mvs.py "another_video.mp4"
76-
```
77-
Note, that the last command will fail because there is no file called `another_video.mp4`.
7869

7970

8071
## Advanced Usage
8172

8273
### Run Tests
8374

84-
To test if everything is installed succesfully you can run the tests with
75+
Before you can run the tests, clone the source code. To this end, change into the desired installation directory on your machine and run
8576
```
86-
sudo ./run.sh python3.10 tests/tests.py
77+
git clone https://github.com/LukasBommes/mv-extractor.git mv_extractor
78+
```
79+
80+
Now, to run the tests from the `mv_extractor` directory with
81+
```
82+
python3 tests/tests.py
8783
```
8884
Confirm that all tests pass.
8985

90-
### Importing mv-extractor into Your Own Scripts
86+
If you are using the Docker image instead of the PyPI package as explained below, you can invoke the tests with
87+
```
88+
sudo ./run.sh python3.10 tests/tests.py
89+
```
90+
91+
### Importing mvextractor into Your Own Scripts
9192

9293
If you want to use the motion vector extractor in your own Python script import it via
9394
```
94-
from mv_extractor import VideoCap
95+
from mvextractor.videocap import VideoCap
9596
```
9697
You can then use it according to the example in `extract_mvs.py`.
9798

9899
Generally, a video file is opened by `VideoCap.open()` and frames, motion vectors, frame types and timestamps are read by calling `VideoCap.read()` repeatedly. Before exiting the program, the video file has to be closed by `VideoCap.release()`. For a more detailed explanation see the API documentation below.
99100

100-
### Building the Docker Image Locally
101+
### Installation via Docker
102+
103+
Instead of installing the motion vector extractor via PyPI you can also use the prebuild Docker image from [DockerHub](https://hub.docker.com/r/lubo1994/mv-extractor). The Docker image contains the motion vector extractor and all its dependencies and comes in handy for quick testing or in case your platform is not compatible with the provided Python package.
104+
105+
#### Prerequisites
106+
107+
To use the Docker image you need to install [Docker](https://docs.docker.com/). Furthermore, you need to clone the source code with
108+
```
109+
git clone https://github.com/LukasBommes/mv-extractor.git mv_extractor
110+
```
111+
112+
#### Run Motion Vector Extraction in Docker
113+
114+
Afterwards, you can run the extraction script in the `mv_extractor` directory as follows
115+
```
116+
sudo ./run.sh python3.10 extract_mvs.py --preview --verbose
117+
```
118+
This pulls the prebuild Docker image from DockerHub and runs the extraction script inside the Docker container.
119+
120+
#### Building the Docker Image Locally (Optional)
101121

102122
This step is not required and for faster installation, we recommend using the prebuilt image.
103123
If you still want to build the Docker image locally, you can do so by running the following command in the `mv_extractor` directory
104124
```
105-
sudo docker build . --tag=mv_extractor
125+
sudo docker build . --tag=mv-extractor
106126
```
107127
Note that building can take more than one hour.
108128

109129
Now, run the docker container with
110130
```
111-
sudo docker run -it --ipc=host --env="DISPLAY" -v $(pwd):/home/video_cap -v /tmp/.X11-unix:/tmp/.X11-unix:rw mv_extractor /bin/bash
131+
sudo docker run -it --ipc=host --env="DISPLAY" -v $(pwd):/home/video_cap -v /tmp/.X11-unix:/tmp/.X11-unix:rw mv-extractor /bin/bash
112132
```
113133

114134

extract_mvs.py

+2-103
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,5 @@
1-
import os
2-
import time
3-
from datetime import datetime
4-
import argparse
5-
6-
import numpy as np
7-
import cv2
8-
9-
from mv_extractor import VideoCap
10-
11-
def draw_motion_vectors(frame, motion_vectors):
12-
if len(motion_vectors) > 0:
13-
num_mvs = np.shape(motion_vectors)[0]
14-
for mv in np.split(motion_vectors, num_mvs):
15-
start_pt = (mv[0, 3], mv[0, 4])
16-
end_pt = (mv[0, 5], mv[0, 6])
17-
cv2.arrowedLine(frame, start_pt, end_pt, (0, 0, 255), 1, cv2.LINE_AA, 0, 0.1)
18-
return frame
1+
from mvextractor.__main__ import main
192

203

214
if __name__ == "__main__":
22-
23-
parser = argparse.ArgumentParser(description='Extract motion vectors from video.')
24-
parser.add_argument('video_url', type=str, nargs='?', default="vid.mp4", help='File path or url of the video stream')
25-
parser.add_argument('--preview', action=argparse.BooleanOptionalAction, default=True, help='Show a preview video with overlaid motion vectors')
26-
parser.add_argument('--verbose', action=argparse.BooleanOptionalAction, default=True, help='Show detailled text output')
27-
parser.add_argument('--dump', action=argparse.BooleanOptionalAction, default=False, help='Dump frames, motion vectors, frame types, and timestamps to output directory')
28-
args = parser.parse_args()
29-
30-
if args.dump:
31-
now = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
32-
for child in ["frames", "motion_vectors"]:
33-
os.makedirs(os.path.join(f"out-{now}", child), exist_ok=True)
34-
35-
cap = VideoCap()
36-
37-
# open the video file
38-
ret = cap.open(args.video_url)
39-
40-
if not ret:
41-
raise RuntimeError(f"Could not open {args.video_url}")
42-
43-
if args.verbose:
44-
print("Sucessfully opened video file")
45-
46-
step = 0
47-
times = []
48-
49-
# continuously read and display video frames and motion vectors
50-
while True:
51-
if args.verbose:
52-
print("Frame: ", step, end=" ")
53-
54-
tstart = time.perf_counter()
55-
56-
# read next video frame and corresponding motion vectors
57-
ret, frame, motion_vectors, frame_type, timestamp = cap.read()
58-
59-
tend = time.perf_counter()
60-
telapsed = tend - tstart
61-
times.append(telapsed)
62-
63-
# if there is an error reading the frame
64-
if not ret:
65-
if args.verbose:
66-
print("No frame read. Stopping.")
67-
break
68-
69-
# print results
70-
if args.verbose:
71-
print("timestamp: {} | ".format(timestamp), end=" ")
72-
print("frame type: {} | ".format(frame_type), end=" ")
73-
74-
print("frame size: {} | ".format(np.shape(frame)), end=" ")
75-
print("motion vectors: {} | ".format(np.shape(motion_vectors)), end=" ")
76-
print("elapsed time: {} s".format(telapsed))
77-
78-
frame = draw_motion_vectors(frame, motion_vectors)
79-
80-
# store motion vectors, frames, etc. in output directory
81-
if args.dump:
82-
cv2.imwrite(os.path.join(f"out-{now}", "frames", f"frame-{step}.jpg"), frame)
83-
np.save(os.path.join(f"out-{now}", "motion_vectors", f"mvs-{step}.npy"), motion_vectors)
84-
with open(os.path.join(f"out-{now}", "timestamps.txt"), "a") as f:
85-
f.write(str(timestamp)+"\n")
86-
with open(os.path.join(f"out-{now}", "frame_types.txt"), "a") as f:
87-
f.write(frame_type+"\n")
88-
89-
90-
step += 1
91-
92-
if args.preview:
93-
cv2.imshow("Frame", frame)
94-
95-
# if user presses "q" key stop program
96-
if cv2.waitKey(1) & 0xFF == ord('q'):
97-
break
98-
99-
if args.verbose:
100-
print("average dt: ", np.mean(times))
101-
102-
cap.release()
103-
104-
# close the GUI window
105-
if args.preview:
106-
cv2.destroyAllWindows()
5+
main()

0 commit comments

Comments
 (0)