Repository: LukasBommes/mv-extractor
Branch: master
Commit: 7d8860e7d276
Files: 1038
Total size: 105.8 KB
Directory structure:
gitextract_5c5m18h4/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── dockerhub.md
├── extract_mvs.py
├── pyproject.toml
├── release.md
├── run.sh
├── setup.py
├── src/
│ └── mvextractor/
│ ├── __init__.py
│ ├── __main__.py
│ ├── mat_to_ndarray.cpp
│ ├── mat_to_ndarray.hpp
│ ├── py_video_cap.cpp
│ ├── pycompat.hpp
│ ├── video_cap.cpp
│ └── video_cap.hpp
├── tests/
│ ├── README.md
│ ├── end_to_end_tests.py
│ ├── reference/
│ │ ├── h264/
│ │ │ ├── frame_types.txt
│ │ │ └── motion_vectors/
│ │ │ ├── mvs-0.npy
│ │ │ ├── mvs-1.npy
│ │ │ ├── mvs-10.npy
│ │ │ ├── mvs-100.npy
│ │ │ ├── mvs-101.npy
│ │ │ ├── mvs-102.npy
│ │ │ ├── mvs-103.npy
│ │ │ ├── mvs-104.npy
│ │ │ ├── mvs-105.npy
│ │ │ ├── mvs-106.npy
│ │ │ ├── mvs-107.npy
│ │ │ ├── mvs-108.npy
│ │ │ ├── mvs-109.npy
│ │ │ ├── mvs-11.npy
│ │ │ ├── mvs-110.npy
│ │ │ ├── mvs-111.npy
│ │ │ ├── mvs-112.npy
│ │ │ ├── mvs-113.npy
│ │ │ ├── mvs-114.npy
│ │ │ ├── mvs-115.npy
│ │ │ ├── mvs-116.npy
│ │ │ ├── mvs-117.npy
│ │ │ ├── mvs-118.npy
│ │ │ ├── mvs-119.npy
│ │ │ ├── mvs-12.npy
│ │ │ ├── mvs-120.npy
│ │ │ ├── mvs-121.npy
│ │ │ ├── mvs-122.npy
│ │ │ ├── mvs-123.npy
│ │ │ ├── mvs-124.npy
│ │ │ ├── mvs-125.npy
│ │ │ ├── mvs-126.npy
│ │ │ ├── mvs-127.npy
│ │ │ ├── mvs-128.npy
│ │ │ ├── mvs-129.npy
│ │ │ ├── mvs-13.npy
│ │ │ ├── mvs-130.npy
│ │ │ ├── mvs-131.npy
│ │ │ ├── mvs-132.npy
│ │ │ ├── mvs-133.npy
│ │ │ ├── mvs-134.npy
│ │ │ ├── mvs-135.npy
│ │ │ ├── mvs-136.npy
│ │ │ ├── mvs-137.npy
│ │ │ ├── mvs-138.npy
│ │ │ ├── mvs-139.npy
│ │ │ ├── mvs-14.npy
│ │ │ ├── mvs-140.npy
│ │ │ ├── mvs-141.npy
│ │ │ ├── mvs-142.npy
│ │ │ ├── mvs-143.npy
│ │ │ ├── mvs-144.npy
│ │ │ ├── mvs-145.npy
│ │ │ ├── mvs-146.npy
│ │ │ ├── mvs-147.npy
│ │ │ ├── mvs-148.npy
│ │ │ ├── mvs-149.npy
│ │ │ ├── mvs-15.npy
│ │ │ ├── mvs-150.npy
│ │ │ ├── mvs-151.npy
│ │ │ ├── mvs-152.npy
│ │ │ ├── mvs-153.npy
│ │ │ ├── mvs-154.npy
│ │ │ ├── mvs-155.npy
│ │ │ ├── mvs-156.npy
│ │ │ ├── mvs-157.npy
│ │ │ ├── mvs-158.npy
│ │ │ ├── mvs-159.npy
│ │ │ ├── mvs-16.npy
│ │ │ ├── mvs-160.npy
│ │ │ ├── mvs-161.npy
│ │ │ ├── mvs-162.npy
│ │ │ ├── mvs-163.npy
│ │ │ ├── mvs-164.npy
│ │ │ ├── mvs-165.npy
│ │ │ ├── mvs-166.npy
│ │ │ ├── mvs-167.npy
│ │ │ ├── mvs-168.npy
│ │ │ ├── mvs-169.npy
│ │ │ ├── mvs-17.npy
│ │ │ ├── mvs-170.npy
│ │ │ ├── mvs-171.npy
│ │ │ ├── mvs-172.npy
│ │ │ ├── mvs-173.npy
│ │ │ ├── mvs-174.npy
│ │ │ ├── mvs-175.npy
│ │ │ ├── mvs-176.npy
│ │ │ ├── mvs-177.npy
│ │ │ ├── mvs-178.npy
│ │ │ ├── mvs-179.npy
│ │ │ ├── mvs-18.npy
│ │ │ ├── mvs-180.npy
│ │ │ ├── mvs-181.npy
│ │ │ ├── mvs-182.npy
│ │ │ ├── mvs-183.npy
│ │ │ ├── mvs-184.npy
│ │ │ ├── mvs-185.npy
│ │ │ ├── mvs-186.npy
│ │ │ ├── mvs-187.npy
│ │ │ ├── mvs-188.npy
│ │ │ ├── mvs-189.npy
│ │ │ ├── mvs-19.npy
│ │ │ ├── mvs-190.npy
│ │ │ ├── mvs-191.npy
│ │ │ ├── mvs-192.npy
│ │ │ ├── mvs-193.npy
│ │ │ ├── mvs-194.npy
│ │ │ ├── mvs-195.npy
│ │ │ ├── mvs-196.npy
│ │ │ ├── mvs-197.npy
│ │ │ ├── mvs-198.npy
│ │ │ ├── mvs-199.npy
│ │ │ ├── mvs-2.npy
│ │ │ ├── mvs-20.npy
│ │ │ ├── mvs-200.npy
│ │ │ ├── mvs-201.npy
│ │ │ ├── mvs-202.npy
│ │ │ ├── mvs-203.npy
│ │ │ ├── mvs-204.npy
│ │ │ ├── mvs-205.npy
│ │ │ ├── mvs-206.npy
│ │ │ ├── mvs-207.npy
│ │ │ ├── mvs-208.npy
│ │ │ ├── mvs-209.npy
│ │ │ ├── mvs-21.npy
│ │ │ ├── mvs-210.npy
│ │ │ ├── mvs-211.npy
│ │ │ ├── mvs-212.npy
│ │ │ ├── mvs-213.npy
│ │ │ ├── mvs-214.npy
│ │ │ ├── mvs-215.npy
│ │ │ ├── mvs-216.npy
│ │ │ ├── mvs-217.npy
│ │ │ ├── mvs-218.npy
│ │ │ ├── mvs-219.npy
│ │ │ ├── mvs-22.npy
│ │ │ ├── mvs-220.npy
│ │ │ ├── mvs-221.npy
│ │ │ ├── mvs-222.npy
│ │ │ ├── mvs-223.npy
│ │ │ ├── mvs-224.npy
│ │ │ ├── mvs-225.npy
│ │ │ ├── mvs-226.npy
│ │ │ ├── mvs-227.npy
│ │ │ ├── mvs-228.npy
│ │ │ ├── mvs-229.npy
│ │ │ ├── mvs-23.npy
│ │ │ ├── mvs-230.npy
│ │ │ ├── mvs-231.npy
│ │ │ ├── mvs-232.npy
│ │ │ ├── mvs-233.npy
│ │ │ ├── mvs-234.npy
│ │ │ ├── mvs-235.npy
│ │ │ ├── mvs-236.npy
│ │ │ ├── mvs-237.npy
│ │ │ ├── mvs-238.npy
│ │ │ ├── mvs-239.npy
│ │ │ ├── mvs-24.npy
│ │ │ ├── mvs-240.npy
│ │ │ ├── mvs-241.npy
│ │ │ ├── mvs-242.npy
│ │ │ ├── mvs-243.npy
│ │ │ ├── mvs-244.npy
│ │ │ ├── mvs-245.npy
│ │ │ ├── mvs-246.npy
│ │ │ ├── mvs-247.npy
│ │ │ ├── mvs-248.npy
│ │ │ ├── mvs-249.npy
│ │ │ ├── mvs-25.npy
│ │ │ ├── mvs-250.npy
│ │ │ ├── mvs-251.npy
│ │ │ ├── mvs-252.npy
│ │ │ ├── mvs-253.npy
│ │ │ ├── mvs-254.npy
│ │ │ ├── mvs-255.npy
│ │ │ ├── mvs-256.npy
│ │ │ ├── mvs-257.npy
│ │ │ ├── mvs-258.npy
│ │ │ ├── mvs-259.npy
│ │ │ ├── mvs-26.npy
│ │ │ ├── mvs-260.npy
│ │ │ ├── mvs-261.npy
│ │ │ ├── mvs-262.npy
│ │ │ ├── mvs-263.npy
│ │ │ ├── mvs-264.npy
│ │ │ ├── mvs-265.npy
│ │ │ ├── mvs-266.npy
│ │ │ ├── mvs-267.npy
│ │ │ ├── mvs-268.npy
│ │ │ ├── mvs-269.npy
│ │ │ ├── mvs-27.npy
│ │ │ ├── mvs-270.npy
│ │ │ ├── mvs-271.npy
│ │ │ ├── mvs-272.npy
│ │ │ ├── mvs-273.npy
│ │ │ ├── mvs-274.npy
│ │ │ ├── mvs-275.npy
│ │ │ ├── mvs-276.npy
│ │ │ ├── mvs-277.npy
│ │ │ ├── mvs-278.npy
│ │ │ ├── mvs-279.npy
│ │ │ ├── mvs-28.npy
│ │ │ ├── mvs-280.npy
│ │ │ ├── mvs-281.npy
│ │ │ ├── mvs-282.npy
│ │ │ ├── mvs-283.npy
│ │ │ ├── mvs-284.npy
│ │ │ ├── mvs-285.npy
│ │ │ ├── mvs-286.npy
│ │ │ ├── mvs-287.npy
│ │ │ ├── mvs-288.npy
│ │ │ ├── mvs-289.npy
│ │ │ ├── mvs-29.npy
│ │ │ ├── mvs-290.npy
│ │ │ ├── mvs-291.npy
│ │ │ ├── mvs-292.npy
│ │ │ ├── mvs-293.npy
│ │ │ ├── mvs-294.npy
│ │ │ ├── mvs-295.npy
│ │ │ ├── mvs-296.npy
│ │ │ ├── mvs-297.npy
│ │ │ ├── mvs-298.npy
│ │ │ ├── mvs-299.npy
│ │ │ ├── mvs-3.npy
│ │ │ ├── mvs-30.npy
│ │ │ ├── mvs-300.npy
│ │ │ ├── mvs-301.npy
│ │ │ ├── mvs-302.npy
│ │ │ ├── mvs-303.npy
│ │ │ ├── mvs-304.npy
│ │ │ ├── mvs-305.npy
│ │ │ ├── mvs-306.npy
│ │ │ ├── mvs-307.npy
│ │ │ ├── mvs-308.npy
│ │ │ ├── mvs-309.npy
│ │ │ ├── mvs-31.npy
│ │ │ ├── mvs-310.npy
│ │ │ ├── mvs-311.npy
│ │ │ ├── mvs-312.npy
│ │ │ ├── mvs-313.npy
│ │ │ ├── mvs-314.npy
│ │ │ ├── mvs-315.npy
│ │ │ ├── mvs-316.npy
│ │ │ ├── mvs-317.npy
│ │ │ ├── mvs-318.npy
│ │ │ ├── mvs-319.npy
│ │ │ ├── mvs-32.npy
│ │ │ ├── mvs-320.npy
│ │ │ ├── mvs-321.npy
│ │ │ ├── mvs-322.npy
│ │ │ ├── mvs-323.npy
│ │ │ ├── mvs-324.npy
│ │ │ ├── mvs-325.npy
│ │ │ ├── mvs-326.npy
│ │ │ ├── mvs-327.npy
│ │ │ ├── mvs-328.npy
│ │ │ ├── mvs-329.npy
│ │ │ ├── mvs-33.npy
│ │ │ ├── mvs-330.npy
│ │ │ ├── mvs-331.npy
│ │ │ ├── mvs-332.npy
│ │ │ ├── mvs-333.npy
│ │ │ ├── mvs-334.npy
│ │ │ ├── mvs-335.npy
│ │ │ ├── mvs-336.npy
│ │ │ ├── mvs-34.npy
│ │ │ ├── mvs-35.npy
│ │ │ ├── mvs-36.npy
│ │ │ ├── mvs-37.npy
│ │ │ ├── mvs-38.npy
│ │ │ ├── mvs-39.npy
│ │ │ ├── mvs-4.npy
│ │ │ ├── mvs-40.npy
│ │ │ ├── mvs-41.npy
│ │ │ ├── mvs-42.npy
│ │ │ ├── mvs-43.npy
│ │ │ ├── mvs-44.npy
│ │ │ ├── mvs-45.npy
│ │ │ ├── mvs-46.npy
│ │ │ ├── mvs-47.npy
│ │ │ ├── mvs-48.npy
│ │ │ ├── mvs-49.npy
│ │ │ ├── mvs-5.npy
│ │ │ ├── mvs-50.npy
│ │ │ ├── mvs-51.npy
│ │ │ ├── mvs-52.npy
│ │ │ ├── mvs-53.npy
│ │ │ ├── mvs-54.npy
│ │ │ ├── mvs-55.npy
│ │ │ ├── mvs-56.npy
│ │ │ ├── mvs-57.npy
│ │ │ ├── mvs-58.npy
│ │ │ ├── mvs-59.npy
│ │ │ ├── mvs-6.npy
│ │ │ ├── mvs-60.npy
│ │ │ ├── mvs-61.npy
│ │ │ ├── mvs-62.npy
│ │ │ ├── mvs-63.npy
│ │ │ ├── mvs-64.npy
│ │ │ ├── mvs-65.npy
│ │ │ ├── mvs-66.npy
│ │ │ ├── mvs-67.npy
│ │ │ ├── mvs-68.npy
│ │ │ ├── mvs-69.npy
│ │ │ ├── mvs-7.npy
│ │ │ ├── mvs-70.npy
│ │ │ ├── mvs-71.npy
│ │ │ ├── mvs-72.npy
│ │ │ ├── mvs-73.npy
│ │ │ ├── mvs-74.npy
│ │ │ ├── mvs-75.npy
│ │ │ ├── mvs-76.npy
│ │ │ ├── mvs-77.npy
│ │ │ ├── mvs-78.npy
│ │ │ ├── mvs-79.npy
│ │ │ ├── mvs-8.npy
│ │ │ ├── mvs-80.npy
│ │ │ ├── mvs-81.npy
│ │ │ ├── mvs-82.npy
│ │ │ ├── mvs-83.npy
│ │ │ ├── mvs-84.npy
│ │ │ ├── mvs-85.npy
│ │ │ ├── mvs-86.npy
│ │ │ ├── mvs-87.npy
│ │ │ ├── mvs-88.npy
│ │ │ ├── mvs-89.npy
│ │ │ ├── mvs-9.npy
│ │ │ ├── mvs-90.npy
│ │ │ ├── mvs-91.npy
│ │ │ ├── mvs-92.npy
│ │ │ ├── mvs-93.npy
│ │ │ ├── mvs-94.npy
│ │ │ ├── mvs-95.npy
│ │ │ ├── mvs-96.npy
│ │ │ ├── mvs-97.npy
│ │ │ ├── mvs-98.npy
│ │ │ └── mvs-99.npy
│ │ ├── mpeg4_part2/
│ │ │ ├── frame_types.txt
│ │ │ └── motion_vectors/
│ │ │ ├── mvs-0.npy
│ │ │ ├── mvs-1.npy
│ │ │ ├── mvs-10.npy
│ │ │ ├── mvs-100.npy
│ │ │ ├── mvs-101.npy
│ │ │ ├── mvs-102.npy
│ │ │ ├── mvs-103.npy
│ │ │ ├── mvs-104.npy
│ │ │ ├── mvs-105.npy
│ │ │ ├── mvs-106.npy
│ │ │ ├── mvs-107.npy
│ │ │ ├── mvs-108.npy
│ │ │ ├── mvs-109.npy
│ │ │ ├── mvs-11.npy
│ │ │ ├── mvs-110.npy
│ │ │ ├── mvs-111.npy
│ │ │ ├── mvs-112.npy
│ │ │ ├── mvs-113.npy
│ │ │ ├── mvs-114.npy
│ │ │ ├── mvs-115.npy
│ │ │ ├── mvs-116.npy
│ │ │ ├── mvs-117.npy
│ │ │ ├── mvs-118.npy
│ │ │ ├── mvs-119.npy
│ │ │ ├── mvs-12.npy
│ │ │ ├── mvs-120.npy
│ │ │ ├── mvs-121.npy
│ │ │ ├── mvs-122.npy
│ │ │ ├── mvs-123.npy
│ │ │ ├── mvs-124.npy
│ │ │ ├── mvs-125.npy
│ │ │ ├── mvs-126.npy
│ │ │ ├── mvs-127.npy
│ │ │ ├── mvs-128.npy
│ │ │ ├── mvs-129.npy
│ │ │ ├── mvs-13.npy
│ │ │ ├── mvs-130.npy
│ │ │ ├── mvs-131.npy
│ │ │ ├── mvs-132.npy
│ │ │ ├── mvs-133.npy
│ │ │ ├── mvs-134.npy
│ │ │ ├── mvs-135.npy
│ │ │ ├── mvs-136.npy
│ │ │ ├── mvs-137.npy
│ │ │ ├── mvs-138.npy
│ │ │ ├── mvs-139.npy
│ │ │ ├── mvs-14.npy
│ │ │ ├── mvs-140.npy
│ │ │ ├── mvs-141.npy
│ │ │ ├── mvs-142.npy
│ │ │ ├── mvs-143.npy
│ │ │ ├── mvs-144.npy
│ │ │ ├── mvs-145.npy
│ │ │ ├── mvs-146.npy
│ │ │ ├── mvs-147.npy
│ │ │ ├── mvs-148.npy
│ │ │ ├── mvs-149.npy
│ │ │ ├── mvs-15.npy
│ │ │ ├── mvs-150.npy
│ │ │ ├── mvs-151.npy
│ │ │ ├── mvs-152.npy
│ │ │ ├── mvs-153.npy
│ │ │ ├── mvs-154.npy
│ │ │ ├── mvs-155.npy
│ │ │ ├── mvs-156.npy
│ │ │ ├── mvs-157.npy
│ │ │ ├── mvs-158.npy
│ │ │ ├── mvs-159.npy
│ │ │ ├── mvs-16.npy
│ │ │ ├── mvs-160.npy
│ │ │ ├── mvs-161.npy
│ │ │ ├── mvs-162.npy
│ │ │ ├── mvs-163.npy
│ │ │ ├── mvs-164.npy
│ │ │ ├── mvs-165.npy
│ │ │ ├── mvs-166.npy
│ │ │ ├── mvs-167.npy
│ │ │ ├── mvs-168.npy
│ │ │ ├── mvs-169.npy
│ │ │ ├── mvs-17.npy
│ │ │ ├── mvs-170.npy
│ │ │ ├── mvs-171.npy
│ │ │ ├── mvs-172.npy
│ │ │ ├── mvs-173.npy
│ │ │ ├── mvs-174.npy
│ │ │ ├── mvs-175.npy
│ │ │ ├── mvs-176.npy
│ │ │ ├── mvs-177.npy
│ │ │ ├── mvs-178.npy
│ │ │ ├── mvs-179.npy
│ │ │ ├── mvs-18.npy
│ │ │ ├── mvs-180.npy
│ │ │ ├── mvs-181.npy
│ │ │ ├── mvs-182.npy
│ │ │ ├── mvs-183.npy
│ │ │ ├── mvs-184.npy
│ │ │ ├── mvs-185.npy
│ │ │ ├── mvs-186.npy
│ │ │ ├── mvs-187.npy
│ │ │ ├── mvs-188.npy
│ │ │ ├── mvs-189.npy
│ │ │ ├── mvs-19.npy
│ │ │ ├── mvs-190.npy
│ │ │ ├── mvs-191.npy
│ │ │ ├── mvs-192.npy
│ │ │ ├── mvs-193.npy
│ │ │ ├── mvs-194.npy
│ │ │ ├── mvs-195.npy
│ │ │ ├── mvs-196.npy
│ │ │ ├── mvs-197.npy
│ │ │ ├── mvs-198.npy
│ │ │ ├── mvs-199.npy
│ │ │ ├── mvs-2.npy
│ │ │ ├── mvs-20.npy
│ │ │ ├── mvs-200.npy
│ │ │ ├── mvs-201.npy
│ │ │ ├── mvs-202.npy
│ │ │ ├── mvs-203.npy
│ │ │ ├── mvs-204.npy
│ │ │ ├── mvs-205.npy
│ │ │ ├── mvs-206.npy
│ │ │ ├── mvs-207.npy
│ │ │ ├── mvs-208.npy
│ │ │ ├── mvs-209.npy
│ │ │ ├── mvs-21.npy
│ │ │ ├── mvs-210.npy
│ │ │ ├── mvs-211.npy
│ │ │ ├── mvs-212.npy
│ │ │ ├── mvs-213.npy
│ │ │ ├── mvs-214.npy
│ │ │ ├── mvs-215.npy
│ │ │ ├── mvs-216.npy
│ │ │ ├── mvs-217.npy
│ │ │ ├── mvs-218.npy
│ │ │ ├── mvs-219.npy
│ │ │ ├── mvs-22.npy
│ │ │ ├── mvs-220.npy
│ │ │ ├── mvs-221.npy
│ │ │ ├── mvs-222.npy
│ │ │ ├── mvs-223.npy
│ │ │ ├── mvs-224.npy
│ │ │ ├── mvs-225.npy
│ │ │ ├── mvs-226.npy
│ │ │ ├── mvs-227.npy
│ │ │ ├── mvs-228.npy
│ │ │ ├── mvs-229.npy
│ │ │ ├── mvs-23.npy
│ │ │ ├── mvs-230.npy
│ │ │ ├── mvs-231.npy
│ │ │ ├── mvs-232.npy
│ │ │ ├── mvs-233.npy
│ │ │ ├── mvs-234.npy
│ │ │ ├── mvs-235.npy
│ │ │ ├── mvs-236.npy
│ │ │ ├── mvs-237.npy
│ │ │ ├── mvs-238.npy
│ │ │ ├── mvs-239.npy
│ │ │ ├── mvs-24.npy
│ │ │ ├── mvs-240.npy
│ │ │ ├── mvs-241.npy
│ │ │ ├── mvs-242.npy
│ │ │ ├── mvs-243.npy
│ │ │ ├── mvs-244.npy
│ │ │ ├── mvs-245.npy
│ │ │ ├── mvs-246.npy
│ │ │ ├── mvs-247.npy
│ │ │ ├── mvs-248.npy
│ │ │ ├── mvs-249.npy
│ │ │ ├── mvs-25.npy
│ │ │ ├── mvs-250.npy
│ │ │ ├── mvs-251.npy
│ │ │ ├── mvs-252.npy
│ │ │ ├── mvs-253.npy
│ │ │ ├── mvs-254.npy
│ │ │ ├── mvs-255.npy
│ │ │ ├── mvs-256.npy
│ │ │ ├── mvs-257.npy
│ │ │ ├── mvs-258.npy
│ │ │ ├── mvs-259.npy
│ │ │ ├── mvs-26.npy
│ │ │ ├── mvs-260.npy
│ │ │ ├── mvs-261.npy
│ │ │ ├── mvs-262.npy
│ │ │ ├── mvs-263.npy
│ │ │ ├── mvs-264.npy
│ │ │ ├── mvs-265.npy
│ │ │ ├── mvs-266.npy
│ │ │ ├── mvs-267.npy
│ │ │ ├── mvs-268.npy
│ │ │ ├── mvs-269.npy
│ │ │ ├── mvs-27.npy
│ │ │ ├── mvs-270.npy
│ │ │ ├── mvs-271.npy
│ │ │ ├── mvs-272.npy
│ │ │ ├── mvs-273.npy
│ │ │ ├── mvs-274.npy
│ │ │ ├── mvs-275.npy
│ │ │ ├── mvs-276.npy
│ │ │ ├── mvs-277.npy
│ │ │ ├── mvs-278.npy
│ │ │ ├── mvs-279.npy
│ │ │ ├── mvs-28.npy
│ │ │ ├── mvs-280.npy
│ │ │ ├── mvs-281.npy
│ │ │ ├── mvs-282.npy
│ │ │ ├── mvs-283.npy
│ │ │ ├── mvs-284.npy
│ │ │ ├── mvs-285.npy
│ │ │ ├── mvs-286.npy
│ │ │ ├── mvs-287.npy
│ │ │ ├── mvs-288.npy
│ │ │ ├── mvs-289.npy
│ │ │ ├── mvs-29.npy
│ │ │ ├── mvs-290.npy
│ │ │ ├── mvs-291.npy
│ │ │ ├── mvs-292.npy
│ │ │ ├── mvs-293.npy
│ │ │ ├── mvs-294.npy
│ │ │ ├── mvs-295.npy
│ │ │ ├── mvs-296.npy
│ │ │ ├── mvs-297.npy
│ │ │ ├── mvs-298.npy
│ │ │ ├── mvs-299.npy
│ │ │ ├── mvs-3.npy
│ │ │ ├── mvs-30.npy
│ │ │ ├── mvs-300.npy
│ │ │ ├── mvs-301.npy
│ │ │ ├── mvs-302.npy
│ │ │ ├── mvs-303.npy
│ │ │ ├── mvs-304.npy
│ │ │ ├── mvs-305.npy
│ │ │ ├── mvs-306.npy
│ │ │ ├── mvs-307.npy
│ │ │ ├── mvs-308.npy
│ │ │ ├── mvs-309.npy
│ │ │ ├── mvs-31.npy
│ │ │ ├── mvs-310.npy
│ │ │ ├── mvs-311.npy
│ │ │ ├── mvs-312.npy
│ │ │ ├── mvs-313.npy
│ │ │ ├── mvs-314.npy
│ │ │ ├── mvs-315.npy
│ │ │ ├── mvs-316.npy
│ │ │ ├── mvs-317.npy
│ │ │ ├── mvs-318.npy
│ │ │ ├── mvs-319.npy
│ │ │ ├── mvs-32.npy
│ │ │ ├── mvs-320.npy
│ │ │ ├── mvs-321.npy
│ │ │ ├── mvs-322.npy
│ │ │ ├── mvs-323.npy
│ │ │ ├── mvs-324.npy
│ │ │ ├── mvs-325.npy
│ │ │ ├── mvs-326.npy
│ │ │ ├── mvs-327.npy
│ │ │ ├── mvs-328.npy
│ │ │ ├── mvs-329.npy
│ │ │ ├── mvs-33.npy
│ │ │ ├── mvs-330.npy
│ │ │ ├── mvs-331.npy
│ │ │ ├── mvs-332.npy
│ │ │ ├── mvs-333.npy
│ │ │ ├── mvs-334.npy
│ │ │ ├── mvs-335.npy
│ │ │ ├── mvs-336.npy
│ │ │ ├── mvs-34.npy
│ │ │ ├── mvs-35.npy
│ │ │ ├── mvs-36.npy
│ │ │ ├── mvs-37.npy
│ │ │ ├── mvs-38.npy
│ │ │ ├── mvs-39.npy
│ │ │ ├── mvs-4.npy
│ │ │ ├── mvs-40.npy
│ │ │ ├── mvs-41.npy
│ │ │ ├── mvs-42.npy
│ │ │ ├── mvs-43.npy
│ │ │ ├── mvs-44.npy
│ │ │ ├── mvs-45.npy
│ │ │ ├── mvs-46.npy
│ │ │ ├── mvs-47.npy
│ │ │ ├── mvs-48.npy
│ │ │ ├── mvs-49.npy
│ │ │ ├── mvs-5.npy
│ │ │ ├── mvs-50.npy
│ │ │ ├── mvs-51.npy
│ │ │ ├── mvs-52.npy
│ │ │ ├── mvs-53.npy
│ │ │ ├── mvs-54.npy
│ │ │ ├── mvs-55.npy
│ │ │ ├── mvs-56.npy
│ │ │ ├── mvs-57.npy
│ │ │ ├── mvs-58.npy
│ │ │ ├── mvs-59.npy
│ │ │ ├── mvs-6.npy
│ │ │ ├── mvs-60.npy
│ │ │ ├── mvs-61.npy
│ │ │ ├── mvs-62.npy
│ │ │ ├── mvs-63.npy
│ │ │ ├── mvs-64.npy
│ │ │ ├── mvs-65.npy
│ │ │ ├── mvs-66.npy
│ │ │ ├── mvs-67.npy
│ │ │ ├── mvs-68.npy
│ │ │ ├── mvs-69.npy
│ │ │ ├── mvs-7.npy
│ │ │ ├── mvs-70.npy
│ │ │ ├── mvs-71.npy
│ │ │ ├── mvs-72.npy
│ │ │ ├── mvs-73.npy
│ │ │ ├── mvs-74.npy
│ │ │ ├── mvs-75.npy
│ │ │ ├── mvs-76.npy
│ │ │ ├── mvs-77.npy
│ │ │ ├── mvs-78.npy
│ │ │ ├── mvs-79.npy
│ │ │ ├── mvs-8.npy
│ │ │ ├── mvs-80.npy
│ │ │ ├── mvs-81.npy
│ │ │ ├── mvs-82.npy
│ │ │ ├── mvs-83.npy
│ │ │ ├── mvs-84.npy
│ │ │ ├── mvs-85.npy
│ │ │ ├── mvs-86.npy
│ │ │ ├── mvs-87.npy
│ │ │ ├── mvs-88.npy
│ │ │ ├── mvs-89.npy
│ │ │ ├── mvs-9.npy
│ │ │ ├── mvs-90.npy
│ │ │ ├── mvs-91.npy
│ │ │ ├── mvs-92.npy
│ │ │ ├── mvs-93.npy
│ │ │ ├── mvs-94.npy
│ │ │ ├── mvs-95.npy
│ │ │ ├── mvs-96.npy
│ │ │ ├── mvs-97.npy
│ │ │ ├── mvs-98.npy
│ │ │ └── mvs-99.npy
│ │ └── rtsp/
│ │ ├── frame_types.txt
│ │ └── motion_vectors/
│ │ ├── mvs-0.npy
│ │ ├── mvs-1.npy
│ │ ├── mvs-10.npy
│ │ ├── mvs-100.npy
│ │ ├── mvs-101.npy
│ │ ├── mvs-102.npy
│ │ ├── mvs-103.npy
│ │ ├── mvs-104.npy
│ │ ├── mvs-105.npy
│ │ ├── mvs-106.npy
│ │ ├── mvs-107.npy
│ │ ├── mvs-108.npy
│ │ ├── mvs-109.npy
│ │ ├── mvs-11.npy
│ │ ├── mvs-110.npy
│ │ ├── mvs-111.npy
│ │ ├── mvs-112.npy
│ │ ├── mvs-113.npy
│ │ ├── mvs-114.npy
│ │ ├── mvs-115.npy
│ │ ├── mvs-116.npy
│ │ ├── mvs-117.npy
│ │ ├── mvs-118.npy
│ │ ├── mvs-119.npy
│ │ ├── mvs-12.npy
│ │ ├── mvs-120.npy
│ │ ├── mvs-121.npy
│ │ ├── mvs-122.npy
│ │ ├── mvs-123.npy
│ │ ├── mvs-124.npy
│ │ ├── mvs-125.npy
│ │ ├── mvs-126.npy
│ │ ├── mvs-127.npy
│ │ ├── mvs-128.npy
│ │ ├── mvs-129.npy
│ │ ├── mvs-13.npy
│ │ ├── mvs-130.npy
│ │ ├── mvs-131.npy
│ │ ├── mvs-132.npy
│ │ ├── mvs-133.npy
│ │ ├── mvs-134.npy
│ │ ├── mvs-135.npy
│ │ ├── mvs-136.npy
│ │ ├── mvs-137.npy
│ │ ├── mvs-138.npy
│ │ ├── mvs-139.npy
│ │ ├── mvs-14.npy
│ │ ├── mvs-140.npy
│ │ ├── mvs-141.npy
│ │ ├── mvs-142.npy
│ │ ├── mvs-143.npy
│ │ ├── mvs-144.npy
│ │ ├── mvs-145.npy
│ │ ├── mvs-146.npy
│ │ ├── mvs-147.npy
│ │ ├── mvs-148.npy
│ │ ├── mvs-149.npy
│ │ ├── mvs-15.npy
│ │ ├── mvs-150.npy
│ │ ├── mvs-151.npy
│ │ ├── mvs-152.npy
│ │ ├── mvs-153.npy
│ │ ├── mvs-154.npy
│ │ ├── mvs-155.npy
│ │ ├── mvs-156.npy
│ │ ├── mvs-157.npy
│ │ ├── mvs-158.npy
│ │ ├── mvs-159.npy
│ │ ├── mvs-16.npy
│ │ ├── mvs-160.npy
│ │ ├── mvs-161.npy
│ │ ├── mvs-162.npy
│ │ ├── mvs-163.npy
│ │ ├── mvs-164.npy
│ │ ├── mvs-165.npy
│ │ ├── mvs-166.npy
│ │ ├── mvs-167.npy
│ │ ├── mvs-168.npy
│ │ ├── mvs-169.npy
│ │ ├── mvs-17.npy
│ │ ├── mvs-170.npy
│ │ ├── mvs-171.npy
│ │ ├── mvs-172.npy
│ │ ├── mvs-173.npy
│ │ ├── mvs-174.npy
│ │ ├── mvs-175.npy
│ │ ├── mvs-176.npy
│ │ ├── mvs-177.npy
│ │ ├── mvs-178.npy
│ │ ├── mvs-179.npy
│ │ ├── mvs-18.npy
│ │ ├── mvs-180.npy
│ │ ├── mvs-181.npy
│ │ ├── mvs-182.npy
│ │ ├── mvs-183.npy
│ │ ├── mvs-184.npy
│ │ ├── mvs-185.npy
│ │ ├── mvs-186.npy
│ │ ├── mvs-187.npy
│ │ ├── mvs-188.npy
│ │ ├── mvs-189.npy
│ │ ├── mvs-19.npy
│ │ ├── mvs-190.npy
│ │ ├── mvs-191.npy
│ │ ├── mvs-192.npy
│ │ ├── mvs-193.npy
│ │ ├── mvs-194.npy
│ │ ├── mvs-195.npy
│ │ ├── mvs-196.npy
│ │ ├── mvs-197.npy
│ │ ├── mvs-198.npy
│ │ ├── mvs-199.npy
│ │ ├── mvs-2.npy
│ │ ├── mvs-20.npy
│ │ ├── mvs-200.npy
│ │ ├── mvs-201.npy
│ │ ├── mvs-202.npy
│ │ ├── mvs-203.npy
│ │ ├── mvs-204.npy
│ │ ├── mvs-205.npy
│ │ ├── mvs-206.npy
│ │ ├── mvs-207.npy
│ │ ├── mvs-208.npy
│ │ ├── mvs-209.npy
│ │ ├── mvs-21.npy
│ │ ├── mvs-210.npy
│ │ ├── mvs-211.npy
│ │ ├── mvs-212.npy
│ │ ├── mvs-213.npy
│ │ ├── mvs-214.npy
│ │ ├── mvs-215.npy
│ │ ├── mvs-216.npy
│ │ ├── mvs-217.npy
│ │ ├── mvs-218.npy
│ │ ├── mvs-219.npy
│ │ ├── mvs-22.npy
│ │ ├── mvs-220.npy
│ │ ├── mvs-221.npy
│ │ ├── mvs-222.npy
│ │ ├── mvs-223.npy
│ │ ├── mvs-224.npy
│ │ ├── mvs-225.npy
│ │ ├── mvs-226.npy
│ │ ├── mvs-227.npy
│ │ ├── mvs-228.npy
│ │ ├── mvs-229.npy
│ │ ├── mvs-23.npy
│ │ ├── mvs-230.npy
│ │ ├── mvs-231.npy
│ │ ├── mvs-232.npy
│ │ ├── mvs-233.npy
│ │ ├── mvs-234.npy
│ │ ├── mvs-235.npy
│ │ ├── mvs-236.npy
│ │ ├── mvs-237.npy
│ │ ├── mvs-238.npy
│ │ ├── mvs-239.npy
│ │ ├── mvs-24.npy
│ │ ├── mvs-240.npy
│ │ ├── mvs-241.npy
│ │ ├── mvs-242.npy
│ │ ├── mvs-243.npy
│ │ ├── mvs-244.npy
│ │ ├── mvs-245.npy
│ │ ├── mvs-246.npy
│ │ ├── mvs-247.npy
│ │ ├── mvs-248.npy
│ │ ├── mvs-249.npy
│ │ ├── mvs-25.npy
│ │ ├── mvs-250.npy
│ │ ├── mvs-251.npy
│ │ ├── mvs-252.npy
│ │ ├── mvs-253.npy
│ │ ├── mvs-254.npy
│ │ ├── mvs-255.npy
│ │ ├── mvs-256.npy
│ │ ├── mvs-257.npy
│ │ ├── mvs-258.npy
│ │ ├── mvs-259.npy
│ │ ├── mvs-26.npy
│ │ ├── mvs-260.npy
│ │ ├── mvs-261.npy
│ │ ├── mvs-262.npy
│ │ ├── mvs-263.npy
│ │ ├── mvs-264.npy
│ │ ├── mvs-265.npy
│ │ ├── mvs-266.npy
│ │ ├── mvs-267.npy
│ │ ├── mvs-268.npy
│ │ ├── mvs-269.npy
│ │ ├── mvs-27.npy
│ │ ├── mvs-270.npy
│ │ ├── mvs-271.npy
│ │ ├── mvs-272.npy
│ │ ├── mvs-273.npy
│ │ ├── mvs-274.npy
│ │ ├── mvs-275.npy
│ │ ├── mvs-276.npy
│ │ ├── mvs-277.npy
│ │ ├── mvs-278.npy
│ │ ├── mvs-279.npy
│ │ ├── mvs-28.npy
│ │ ├── mvs-280.npy
│ │ ├── mvs-281.npy
│ │ ├── mvs-282.npy
│ │ ├── mvs-283.npy
│ │ ├── mvs-284.npy
│ │ ├── mvs-285.npy
│ │ ├── mvs-286.npy
│ │ ├── mvs-287.npy
│ │ ├── mvs-288.npy
│ │ ├── mvs-289.npy
│ │ ├── mvs-29.npy
│ │ ├── mvs-290.npy
│ │ ├── mvs-291.npy
│ │ ├── mvs-292.npy
│ │ ├── mvs-293.npy
│ │ ├── mvs-294.npy
│ │ ├── mvs-295.npy
│ │ ├── mvs-296.npy
│ │ ├── mvs-297.npy
│ │ ├── mvs-298.npy
│ │ ├── mvs-299.npy
│ │ ├── mvs-3.npy
│ │ ├── mvs-30.npy
│ │ ├── mvs-300.npy
│ │ ├── mvs-301.npy
│ │ ├── mvs-302.npy
│ │ ├── mvs-303.npy
│ │ ├── mvs-304.npy
│ │ ├── mvs-305.npy
│ │ ├── mvs-306.npy
│ │ ├── mvs-307.npy
│ │ ├── mvs-308.npy
│ │ ├── mvs-309.npy
│ │ ├── mvs-31.npy
│ │ ├── mvs-310.npy
│ │ ├── mvs-311.npy
│ │ ├── mvs-312.npy
│ │ ├── mvs-313.npy
│ │ ├── mvs-314.npy
│ │ ├── mvs-315.npy
│ │ ├── mvs-316.npy
│ │ ├── mvs-317.npy
│ │ ├── mvs-318.npy
│ │ ├── mvs-319.npy
│ │ ├── mvs-32.npy
│ │ ├── mvs-320.npy
│ │ ├── mvs-321.npy
│ │ ├── mvs-322.npy
│ │ ├── mvs-323.npy
│ │ ├── mvs-324.npy
│ │ ├── mvs-325.npy
│ │ ├── mvs-326.npy
│ │ ├── mvs-327.npy
│ │ ├── mvs-328.npy
│ │ ├── mvs-329.npy
│ │ ├── mvs-33.npy
│ │ ├── mvs-330.npy
│ │ ├── mvs-331.npy
│ │ ├── mvs-332.npy
│ │ ├── mvs-333.npy
│ │ ├── mvs-334.npy
│ │ ├── mvs-335.npy
│ │ ├── mvs-34.npy
│ │ ├── mvs-35.npy
│ │ ├── mvs-36.npy
│ │ ├── mvs-37.npy
│ │ ├── mvs-38.npy
│ │ ├── mvs-39.npy
│ │ ├── mvs-4.npy
│ │ ├── mvs-40.npy
│ │ ├── mvs-41.npy
│ │ ├── mvs-42.npy
│ │ ├── mvs-43.npy
│ │ ├── mvs-44.npy
│ │ ├── mvs-45.npy
│ │ ├── mvs-46.npy
│ │ ├── mvs-47.npy
│ │ ├── mvs-48.npy
│ │ ├── mvs-49.npy
│ │ ├── mvs-5.npy
│ │ ├── mvs-50.npy
│ │ ├── mvs-51.npy
│ │ ├── mvs-52.npy
│ │ ├── mvs-53.npy
│ │ ├── mvs-54.npy
│ │ ├── mvs-55.npy
│ │ ├── mvs-56.npy
│ │ ├── mvs-57.npy
│ │ ├── mvs-58.npy
│ │ ├── mvs-59.npy
│ │ ├── mvs-6.npy
│ │ ├── mvs-60.npy
│ │ ├── mvs-61.npy
│ │ ├── mvs-62.npy
│ │ ├── mvs-63.npy
│ │ ├── mvs-64.npy
│ │ ├── mvs-65.npy
│ │ ├── mvs-66.npy
│ │ ├── mvs-67.npy
│ │ ├── mvs-68.npy
│ │ ├── mvs-69.npy
│ │ ├── mvs-7.npy
│ │ ├── mvs-70.npy
│ │ ├── mvs-71.npy
│ │ ├── mvs-72.npy
│ │ ├── mvs-73.npy
│ │ ├── mvs-74.npy
│ │ ├── mvs-75.npy
│ │ ├── mvs-76.npy
│ │ ├── mvs-77.npy
│ │ ├── mvs-78.npy
│ │ ├── mvs-79.npy
│ │ ├── mvs-8.npy
│ │ ├── mvs-80.npy
│ │ ├── mvs-81.npy
│ │ ├── mvs-82.npy
│ │ ├── mvs-83.npy
│ │ ├── mvs-84.npy
│ │ ├── mvs-85.npy
│ │ ├── mvs-86.npy
│ │ ├── mvs-87.npy
│ │ ├── mvs-88.npy
│ │ ├── mvs-89.npy
│ │ ├── mvs-9.npy
│ │ ├── mvs-90.npy
│ │ ├── mvs-91.npy
│ │ ├── mvs-92.npy
│ │ ├── mvs-93.npy
│ │ ├── mvs-94.npy
│ │ ├── mvs-95.npy
│ │ ├── mvs-96.npy
│ │ ├── mvs-97.npy
│ │ ├── mvs-98.npy
│ │ └── mvs-99.npy
│ ├── tools/
│ │ └── live555MediaServer
│ └── unit_tests.py
└── vid_h264.264
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
# On a push to any branch this workflow:
# - builds the Docker image,
# - build wheels for different Python versions,
# - installs the wheel for each Python version and runs the unit tests
# against newest and oldest versions of dependencies.
# On manual dispatch this workflow:
# - pushes the previously built Docker image to DockerHub with tag "dev".
name: ci and release
on:
workflow_dispatch:
pull_request:
jobs:
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and export
uses: docker/build-push-action@v6
with:
context: .
tags: mv-extractor:local
outputs: type=docker,dest=/tmp/image.tar
cache-from: type=registry,ref=lubo1994/mv-extractor:buildcache
cache-to: type=registry,ref=lubo1994/mv-extractor:buildcache,mode=max
- name: Upload Docker image as artifact
uses: actions/upload-artifact@v4
with:
name: mv-extractor-docker-image
path: /tmp/image.tar
test_docker:
name: Run unit tests in Docker container (only for the Python version used in the Dockerfile command)
runs-on: ubuntu-latest
needs:
- build_docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifact containing Docker image
uses: actions/download-artifact@v4
with:
name: mv-extractor-docker-image
path: /tmp
- name: Load Docker image
run: |
docker load --input /tmp/image.tar
- name: Run unit tests
run: |
docker run -v ${{ github.workspace }}:/home/video_cap \
mv-extractor:local \
/bin/bash -c ' \
yum install -y compat-openssl10 && \
python3.12 -m unittest discover -s tests -p "*tests.py"
'
build_and_test_wheels:
name: Build wheels for cp${{ matrix.python }}-${{ matrix.platform_id }}
runs-on: ${{ matrix.os }}
needs:
- build_docker
strategy:
# Ensure that a wheel builder finishes even if another fails
fail-fast: false
matrix:
include:
- os: ubuntu-latest
python: 39
bitness: 64
platform_id: manylinux_x86_64
manylinux_image: mv-extractor:local
numpy_min_version: "numpy==1.19.3"
opencv_min_version: "opencv-python==4.4.0.46"
- os: ubuntu-latest
python: 310
bitness: 64
platform_id: manylinux_x86_64
manylinux_image: mv-extractor:local
numpy_min_version: "numpy==1.21.2"
opencv_min_version: "opencv-python==4.5.4.60"
- os: ubuntu-latest
python: 311
bitness: 64
platform_id: manylinux_x86_64
manylinux_image: mv-extractor:local
numpy_min_version: "numpy==1.23.3"
opencv_min_version: "opencv-python==4.7.0.72"
- os: ubuntu-latest
python: 312
bitness: 64
platform_id: manylinux_x86_64
manylinux_image: mv-extractor:local
numpy_min_version: "numpy==1.26.0"
opencv_min_version: "opencv-python==4.9.0.80"
- os: ubuntu-latest
python: 313
bitness: 64
platform_id: manylinux_x86_64
manylinux_image: mv-extractor:local
numpy_min_version: "numpy==2.2.6"
opencv_min_version: "opencv-python==4.12.0.88"
- os: ubuntu-latest
python: 314
bitness: 64
platform_id: manylinux_x86_64
manylinux_image: mv-extractor:local
numpy_min_version: "numpy==2.2.6"
opencv_min_version: "opencv-python==4.12.0.88"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifact containing Docker image
uses: actions/download-artifact@v4
with:
name: mv-extractor-docker-image
path: /tmp
- name: Load Docker image
run: |
docker load --input /tmp/image.tar
- name: Build and test wheels
uses: pypa/cibuildwheel@v3.2.1
env:
CIBW_PLATFORM: linux
CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }}
# Disable building PyPy wheels on all platforms
CIBW_SKIP: pp*
CIBW_ARCHS: x86_64
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux_image }}
#CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.manylinux_image }}
CIBW_BUILD_FRONTEND: build
CIBW_TEST_COMMAND: |
echo "Running unit tests" && \
yum install -y compat-openssl10 && \
PROJECT_ROOT={project} python3 -m unittest discover -s {project}/tests -p "*tests.py" && \
echo "Running unit tests against oldest supported versions of dependencies" && \
python3 -m pip install ${{ matrix.numpy_min_version }} ${{ matrix.opencv_min_version }} && \
PROJECT_ROOT={project} python3 -m unittest discover -s {project}/tests -p "*tests.py"
CIBW_BUILD_VERBOSITY: 1
- uses: actions/upload-artifact@v4
with:
name: python-wheel-${{ matrix.python }}
path: ./wheelhouse/*.whl
push_docker:
name: Push Docker image to DockerHub
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
needs:
- build_docker
- test_docker
- build_and_test_wheels
steps:
- name: Download artifact containing Docker image
uses: actions/download-artifact@v4
with:
name: mv-extractor-docker-image
path: /tmp
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Load and push Docker image
run: |
docker load --input /tmp/image.tar
docker tag mv-extractor:local lubo1994/mv-extractor:dev
docker push lubo1994/mv-extractor:dev
================================================
FILE: .gitignore
================================================
/build/
/dist/
/wheelhouse/
*.egg-info
*.egg
.eggs
__pycache__/
/venv3.*/
env/
out-*/
*.tar
a.out
*.so
================================================
FILE: Dockerfile
================================================
FROM quay.io/pypa/manylinux_2_28_x86_64 AS builder
# Install build tools
RUN yum update -y && \
yum install -y \
wget \
unzip \
git \
make \
cmake \
gcc-toolset-10 \
gcc-c++ \
pkgconfig \
libtool && \
yum clean all
# Activate specific version of gcc toolset (newer versions of gcc fail to build old versions of ffmpeg)
ENV PATH="/opt/rh/gcc-toolset-10/root/usr/bin:$PATH"
ENV LD_LIBRARY_PATH="/opt/rh/gcc-toolset-10/root/usr/lib64:$LD_LIBRARY_PATH"
# Install OpenCV
ARG OPENCV_VERSION="4.12.0"
WORKDIR /opt
RUN wget -O opencv.zip https://github.com/opencv/opencv/archive/"$OPENCV_VERSION".zip && \
unzip opencv.zip && \
mv opencv-"$OPENCV_VERSION" opencv && \
mkdir opencv/build && \
cd opencv/build && \
cmake \
-D CMAKE_BUILD_TYPE=RELEASE \
-D OPENCV_GENERATE_PKGCONFIG=YES \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_ENABLE_NONFREE=OFF \
-D BUILD_LIST=core,imgproc \
.. && \
make -j $(nproc) && \
make install && \
ldconfig && \
rm -rf ../../opencv.zip && \
rm -rf ../../opencv
# Install FFMPEG
WORKDIR /opt/ffmpeg_sources
RUN curl -O -L https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.bz2 && \
tar xjvf nasm-2.15.05.tar.bz2 && \
cd nasm-2.15.05 && \
./autogen.sh && \
./configure --disable-shared --enable-static && \
make -j $(nproc) && \
make install && \
rm -rf ../nasm-2.15.05.tar.bz2 && \
rm -rf ../nasm-2.15.05
WORKDIR /opt/ffmpeg_sources
RUN curl -O -L https://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz && \
tar xzvf yasm-1.3.0.tar.gz && \
cd yasm-1.3.0 && \
./configure --disable-shared --enable-static && \
make -j $(nproc) && \
make install && \
rm -rf ../yasm-1.3.0.tar.gz && \
rm -rf ../yasm-1.3.0
WORKDIR /opt/ffmpeg_sources
RUN git clone --branch stable --depth 1 https://code.videolan.org/videolan/x264.git && \
cd x264 && \
./configure --disable-shared --enable-static --enable-pic && \
make -j $(nproc) && \
make install && \
rm -rf ../x264
ARG FFMPEG_VERSION="4.1.3"
WORKDIR /opt/ffmpeg_sources
RUN wget -O ffmpeg-snapshot.tar.bz2 https://ffmpeg.org/releases/ffmpeg-"$FFMPEG_VERSION".tar.bz2 && \
mkdir -p ffmpeg && \
tar xjvf ffmpeg-snapshot.tar.bz2 -C ffmpeg --strip-components=1 && \
rm -rf ffmpeg-snapshot.tar.bz2
WORKDIR /opt/ffmpeg_sources/ffmpeg
RUN ./configure \
--pkg-config-flags="--static" \
--extra-cflags="-I/usr/local/include" \
--extra-ldflags="-L/usr/local/lib" \
--extra-libs=-lpthread \
--extra-libs=-lm \
--enable-static \
--disable-shared \
--enable-gpl \
--enable-libx264 \
--enable-nonfree \
--enable-pic && \
make -j $(nproc) && \
make install && \
rm -rf ../ffmpeg
FROM quay.io/pypa/manylinux_2_28_x86_64
# copy libraries
WORKDIR /usr/local/lib
COPY --from=builder /usr/local/lib .
WORKDIR /usr/local/lib64
COPY --from=builder /usr/local/lib64 .
WORKDIR /usr/local/include
COPY --from=builder /usr/local/include .
WORKDIR /usr/local/lib
COPY --from=builder /usr/local/lib .
# Set environment variables
ENV PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib64/pkgconfig"
ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib64"
WORKDIR /home/video_cap
COPY pyproject.toml /home/video_cap/
COPY setup.py /home/video_cap/
COPY src /home/video_cap/src/
COPY README.md /home/video_cap/
# Install Python package
RUN python3.12 -m pip install .
# Location of the "extract_mvs" script
ENV PATH="$PATH:/opt/python/cp312-cp312/bin"
CMD ["sh", "-c", "tail -f /dev/null"]
================================================
FILE: LICENSE
================================================
The MIT License
Copyright (c) 2010-2024 Lukas Bommes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
recursive-include ffmpeg_patch *
recursive-include src *
recursive-include tests *
include LICENSE
include pyproject.toml
include extract_mvs.py
include setup.py
include vid_h264.mp4
include vid_mpeg4_part2.mp4
include vid_h264.264
================================================
FILE: README.md
================================================
Motion Vector Extractor
This tool extracts motion vectors, frames, and frame types from H.264 and MPEG-4 Part 2 encoded videos.
A replacement for OpenCV's [VideoCapture](https://docs.opencv.org/4.1.0/d8/dfe/classcv_1_1VideoCapture.html) that returns for each frame:
- Frame type (I, P, or B)
- motion vectors
- Optional decoded frame as BGR image
Frame decoding can be skipped for very fast motion vector extraction, ideal for, e.g., fast visual object tracking. Both a C++ and a Python API is provided.
The image below shows a video frame with extracted motion vectors overlaid.

Note on Deprecation of Timestamp Extraction
Versions 1.x of the motion vector extractor additionally returned the timestamps of video frames. For RTSP streams, the UTC wall time of when the sender transmitted a frame was returned (rather than the more easily retrievable reception timestamp).
Since this feature required patching FFmpeg internals, it became difficult to maintain and prevented compatibility with newer versions of FFmpeg.
As a result, timestamp extraction was removed in the 2.0.0 release. If you rely on this feature, please use version **1.1.0**.
## News
### Recent Changes in Release 2.0.0
- New motion-vectors-only mode, in which frame decoding is skipped for better performance (thanks to [@microa](https://github.com/LukasBommes/mv-extractor/pull/78))
- Dropped extraction of timestamps as this feature was complex and difficult to maintain. Note the breaking API change to the `read` and `retrieve` methods of the `VideoCapture` class
```diff
- ret, frame, motion_vectors, frame_type, timestamp = cap.read()
+ ret, frame, motion_vectors, frame_type = cap.read()
```
- Added support for Python 3.13 and 3.14
- Moved installation of FFMPEG and OpenCV from script files directly into Dockerfile
- Improved quickstart section of the readme
## Quickstart
### Step 1: Install
```bash
pip install motion-vector-extractor
```
Note, that we currently provide the package only for x86-64 linux, such as Ubuntu or Debian, and Python 3.9 to 3.14. If you are on a different platform, please use the Docker image as described [below](#installation-via-docker).
### Step 2: Extract Motion Vectors
You can follow along the examples below using the example video [`vid_h264.mp4`](https://github.com/LukasBommes/mv-extractor/blob/master/vid_h264.mp4) from the repo.
#### Command Line
```bash
# Extract motion vectors and show live preview
extract_mvs vid_h264.mp4 --preview --verbose
# Extract motion vectors and skip frame decoding (faster)
extract_mvs vid_h264.mp4 --verbose --skip-decoding-frames
# Extract and store motion vectors and frames to disk without showing live preview
extract_mvs vid_h264.mp4 --dump
# See all available options
extract_mvs -h
```
#### Python API
```python
from mvextractor.videocap import VideoCap
cap = VideoCap()
cap.open("vid_h264.mp4")
# (optional) skip decoding frames
cap.set_decode_frames(False)
while True:
ret, frame, motion_vectors, frame_type = cap.read()
if not ret:
break
print(f"Num. motion vectors: {len(motion_vectors)}")
print(f"Frame type: {frame_type}")
if frame is not None:
print(f"Frame size: {frame.shape}")
cap.release()
```
## Advanced Usage
### Installation via Docker
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.
#### Prerequisites
To use the Docker image you need to install [Docker](https://docs.docker.com/). Furthermore, you need to clone the source code with
```bash
git clone https://github.com/LukasBommes/mv-extractor.git mv_extractor
```
#### Run Motion Vector Extraction in Docker
Afterwards, you can run the extraction script in the `mv_extractor` directory as follows
```bash
./run.sh python3.12 extract_mvs.py vid_h264.mp4 --preview --verbose
```
This pulls the prebuild Docker image from DockerHub and runs the extraction script inside the Docker container.
#### Building the Docker Image Locally (Optional)
This step is not required and for faster installation, we recommend using the prebuilt image.
If you still want to build the Docker image locally, you can do so by running the following command in the `mv_extractor` directory
```bash
docker build . --tag=mv-extractor
```
Note that building can take more than one hour.
Now, run the docker container with
```bash
docker run -it --ipc=host --env="DISPLAY" -v $(pwd):/home/video_cap -v /tmp/.X11-unix:/tmp/.X11-unix:rw mv-extractor /bin/bash
```
## Python API
This module provides a Python API which is very similar to that of OpenCV [VideoCapture](https://docs.opencv.org/4.1.0/d8/dfe/classcv_1_1VideoCapture.html). Using the Python API is the recommended way of using the H.264 Motion Vector Capture class.
#### Class :: VideoCap()
| Methods | Description |
| --- | --- |
| VideoCap() | Constructor |
| open() | Open a video file or url |
| grab() | Reads the next video frame and motion vectors from the stream |
| retrieve() | Decodes and returns the grabbed frame and motion vectors |
| read() | Convenience function which combines a call of grab() and retrieve() |
| release() | Close a video file or url and release all ressources |
| set_decode_frames() | Enable/disable decoding of video frames |
| Attributes | Description |
| --- | --- |
| decode_frames | Getter to check if frame decoding is enabled (True) or skipped (False) |
##### Method :: VideoCap()
Constructor. Takes no input arguments and returns nothing.
##### Method :: open()
Open a video file or url. The stream must be H264 encoded. Otherwise, undesired behaviour is likely.
| Parameter | Type | Description |
| --- | --- | --- |
| url | string | Relative or fully specified file path or an url specifying the location of the video stream. Example "vid.flv" for a video file located in the same directory as the source files. Or "rtsp://xxx.xxx.xxx.xxx:554" for an IP camera streaming via RTSP. |
| Returns | Type | Description |
| --- | --- | --- |
| success | bool | True if video file or url could be opened successfully, false otherwise. |
##### Method :: grab()
Reads the next video frame and motion vectors from the stream, but does not yet decode it. Thus, grab() is fast. A subsequent call to retrieve() is needed to decode and return the frame and motion vectors. the purpose of splitting up grab() and retrieve() is to provide a means to capture frames in multi-camera scenarios which are as close in time as possible. To do so, first call grab() on all cameras and afterwards call retrieve() on all cameras.
Takes no input arguments.
| Returns | Type | Description |
| --- | --- | --- |
| success | bool | True if next frame and motion vectors could be grabbed successfully, false otherwise. |
##### Method :: retrieve()
Decodes and returns the grabbed frame and motion vectors. Prior to calling retrieve() on a stream, grab() needs to have been called and returned successfully.
Takes no input arguments and returns a tuple with the elements described in the table below.
| Index | Name | Type | Description |
| --- | --- | --- | --- |
| 0 | success | bool | True in case the frame and motion vectors could be retrieved sucessfully, false otherwise or in case the end of stream is reached. When false, the other tuple elements are set to empty numpy arrays or 0. |
| 1 | frame | numpy array | Array of dtype uint8 shape (h, w, 3) containing the decoded video frame. w and h are the width and height of this frame in pixels. Channels are in BGR order. If no frame could be decoded an empty numpy ndarray of shape (0, 0, 3) and dtype uint8 is returned. If frame decoding is disabled with set_decode_frames(False) None is returned instead. |
| 2 | motion vectors | numpy array | Array of dtype int32 and shape (N, 10) containing the N motion vectors of the frame. Each row of the array corresponds to one motion vector. If no motion vectors are present in a frame, e.g. if the frame is an `I` frame an empty numpy array of shape (0, 10) and dtype int32 is returned. The columns of each vector have the following meaning (also refer to [AVMotionVector](https://ffmpeg.org/doxygen/4.1/structAVMotionVector.html) in FFMPEG documentation):
- 0: `source`: offset of the reference frame from the current frame. The reference frame is the frame where the motion vector points to and where the corresponding macroblock comes from. If `source < 0`, the reference frame is in the past. For `source > 0` the it is in the future (in display order).
- 1: `w`: width of the vector's macroblock.
- 2: `h`: height of the vector's macroblock.
- 3: `src_x`: x-location (in pixels) where the motion vector points to in the reference frame.
- 4: `src_y`: y-location (in pixels) where the motion vector points to in the reference frame.
- 5: `dst_x`: x-location of the vector's origin in the current frame (in pixels). Corresponds to the x-center coordinate of the corresponding macroblock.
- 6: `dst_y`: y-location of the vector's origin in the current frame (in pixels). Corresponds to the y-center coordinate of the corresponding macroblock.
- 7: `motion_x`: Macroblock displacement in x-direction, multiplied by `motion_scale` to become integer. Used to compute fractional value for `src_x` as `src_x = dst_x + motion_x / motion_scale`.
- 8: `motion_y`: Macroblock displacement in y-direction, multiplied by `motion_scale` to become integer. Used to compute fractional value for `src_y` as `src_y = dst_y + motion_y / motion_scale`.
- 9: `motion_scale`: see definiton of columns 7 and 8. Used to scale up the motion components to integer values. E.g. if `motion_scale = 4`, motion components can be integer values but encode a float with 1/4 pixel precision.
Note: `src_x` and `src_y` are only in integer resolution. They are contained in the [AVMotionVector](https://ffmpeg.org/doxygen/4.1/structAVMotionVector.html) struct and exported only for the sake of completeness. Use equations in field 7 and 8 to get more accurate fractional values for `src_x` and `src_y`. |
| 3 | frame_type | string | Unicode string representing the type of frame. Can be `"I"` for a keyframe, `"P"` for a frame with references to only past frames and `"B"` for a frame with references to both past and future frames. A `"?"` string indicates an unknown frame type. |
##### Method :: read()
Convenience function which internally calls first grab() and then retrieve(). It takes no arguments and returns the same values as retrieve().
##### Method :: release()
Close a video file or url and release all ressources. Takes no input arguments and returns nothing.
##### Method :: set_decode_frames()
Enable/disable decoding of video frames. May be called anytime, even mid-stream. Returns nothing.
| Parameter | Type | Description |
| --- | --- | --- |
| enable | bool | If True (default) RGB frames are decoded and returned in addition to extracted motion vectors. If False, frame decoding is skipped, yielding much higher extraction througput. |
## C++ API
The C++ API differs from the Python API in what parameters the methods expect and what values they return. Refer to the docstrings in `src/video_cap.hpp`.
## Theory
What follows is a short explanation of the data returned by the `VideoCap` class. Also refer this [excellent book](https://dl.acm.org/citation.cfm?id=1942939) by Iain E. Richardson for more details.
##### Frame
The decoded video frame. Nothing special about that.
##### Motion Vectors
H.264 and MPEG-4 Part 2 use different techniques to reduce the size of a raw video frame prior to sending it over a network or storing it into a file. One of those techniques is motion estimation and prediction of future frames based on previous or future frames. Each frame is segmented into macroblocks of e.g. 16 pixel x 16 pixel. During encoding motion estimation matches every macroblock to a similar looking macroblock in a previously encoded frame (note that this frame can also be a future frame since encoding and presentation order might differ). This allows to transmit only those motion vectors and the reference macroblock instead of all macroblocks, effectively reducing the amount of transmitted or stored data.
Motion vectors correlate directly with motion in the video scene and are useful for various computer vision tasks, such as visual object tracking.
In MPEG-4 Part 2 macroblocks are always 16 pixel x 16 pixel. In H.264 macroblocks can be 16x16, 16x8, 8x16, 8x8, 8x4, 4x8, or 4x4 in size.
##### Frame Types
The frame type is either "P", "B" or "I" and refers to the H.264 encoding mode of the current frame. An "I" frame is send fully over the network and serves as a reference for "P" and "B" frames for which only differences to previously decoded frames are transmitted. Those differences are encoded via motion vectors. As a consequence, for an "I" frame no motion vectors are returned by this library. The difference between "P" and "B" frames is that "P" frames refer only to past frames, whereas "B" frames have motion vectors which refer to both past and future frames. References to future frames are possible even with live streams because the decoding order of frames differs from the presentation order.
## About
This software is maintained by [**Lukas Bommes**](https://lukasbommes.de/).
It is based on [MV-Tractus](https://github.com/jishnujayakumar/MV-Tractus/tree/master/include) and OpenCV's [videoio module](https://github.com/opencv/opencv/tree/master/modules/videoio).
#### License
This project is licensed under the MIT License - see the [LICENSE](https://github.com/LukasBommes/mv-extractor/blob/master/LICENSE) file for details.
#### Citation
If you use our work for academic research please cite
```
@INPROCEEDINGS{9248145,
author={L. {Bommes} and X. {Lin} and J. {Zhou}},
booktitle={2020 15th IEEE Conference on Industrial Electronics and Applications (ICIEA)},
title={MVmed: Fast Multi-Object Tracking in the Compressed Domain},
year={2020},
volume={},
number={},
pages={1419-1424},
doi={10.1109/ICIEA48937.2020.9248145}}
```
================================================
FILE: dockerhub.md
================================================
# Motion Vector Extractor
The [motion vector extractor](https://github.com/LukasBommes/mv-extractor) is a tool to extract frames, motion vectors and frame types from H.264 and MPEG-4 Part 2 encoded videos. The tool provides a single class, which serves as a replacement for OpenCV's [VideoCapture](https://docs.opencv.org/4.1.0/d8/dfe/classcv_1_1VideoCapture.html) and can be used to read and decode video frames from a H.264 or MPEG-4 Part 2 encoded video stream/file.
This Docker image is based on the [manylinux_2_28](https://github.com/pypa/manylinux) image and serves two purposes:
1. It contains all dependencies to run the motion vector extractor and its test suite.
2. It functions as build environment for building the [Python package](https://pypi.org/project/motion-vector-extractor/) of the motion vector extraction for all supported Python versions.
## Tags with respective Dockerfile links
- [`v1.1.0`, `latest`](https://github.com/LukasBommes/mv-extractor/blob/c56b94b9ec7e96e273e67eb5cf19f0e6b927f68b/Dockerfile)
- [`v1.0.6`](https://github.com/LukasBommes/mv-extractor/blob/75424afe230f9847f3e86e243f46d3105eeba858/Dockerfile)
- [`v1.0.5`](https://github.com/LukasBommes/mv-extractor/blob/ac539243f6cd7cc1d9640d8ce52ba1814a3cbc7d/Dockerfile)
- [`v1.0.4`](https://github.com/LukasBommes/mv-extractor/blob/94a79e0ce72446beb7b3862f8ed04a1cbce0d1a3/Dockerfile)
- [`v1.0.3`](https://github.com/LukasBommes/mv-extractor/blob/2ccce5b85e1c9cf813271e443490981c5773dc02/Dockerfile)
- [`v1.0.2`](https://github.com/LukasBommes/mv-extractor/blob/4dc77fe5681d55820b43657c63c81294bf47a0bc/Dockerfile)
- [`v1.0.1`](https://github.com/LukasBommes/mv-extractor/blob/17ae26680194b49996e01397871bef857064514f/Dockerfile)
- [`v1.0.0`](https://github.com/LukasBommes/mv-extractor/blob/4b44302a44e78618aeabde95ee02cecee311b456/Dockerfile)
Images tagged with `dev` and `buildcache` are intermediate artefacts generated by the CI and should not be used directly.
## Usage
Pull and run the motion vector extractor with
```cmd
docker run lubo1994/mv-extractor:latest extract_mvs -h
```
Map a video file into the container and extract motion vectors (replace with the actual filename)
```cmd
docker run -v ./:/home/video_cap/ lubo1994/mv-extractor:latest extract_mvs --verbose
```
If you want to use the graphical preview, you have to supply additional arguments to the docker run command
```cmd
docker run -it --ipc=host --env="DISPLAY" -v ./:/home/video_cap/ -v /tmp/.X11-unix:/tmp/.X11-unix:rw lubo1994/mv-extractor:latest extract_mvs --preview
```
For more details on the usage see the [project homepage](https://github.com/LukasBommes/mv-extractor).
## About
This software is written by [**Lukas Bommes**](https://lukasbommes.de/) and licensed under the [MIT License](https://github.com/LukasBommes/mv-extractor/blob/master/LICENSE).
If you use the project for academic research please cite
```text
@INPROCEEDINGS{9248145,
author={L. {Bommes} and X. {Lin} and J. {Zhou}},
booktitle={2020 15th IEEE Conference on Industrial Electronics and Applications (ICIEA)},
title={MVmed: Fast Multi-Object Tracking in the Compressed Domain},
year={2020},
volume={},
number={},
pages={1419-1424},
doi={10.1109/ICIEA48937.2020.9248145}}
```
================================================
FILE: extract_mvs.py
================================================
from mvextractor.__main__ import main
if __name__ == "__main__":
main()
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = [
"setuptools>=61.0",
"pkgconfig>=1.5.1",
"numpy>=2.0.0"
]
build-backend = "setuptools.build_meta"
================================================
FILE: release.md
================================================
# Create a new release
### Step 1) Bump version
Bump the version in `setup.py`
### Step 2) Push code
Make changes, commit and push.
### Step 3) Run build workflow
On GitHub go to the repo's "Actions" tab and manually trigger the "build" workflow. The build workflow builds the Docker image and wheels. The Docker image is automatically pushed to Dockerhub. The wheels need to be manually uploaded to PyPI as explained below.
### Step 4) Create tag and release
Now, create a tag with the same version just entered in the `setup.py` and push that tag to the remote.
```
git tag vx.x.x
git push origin vx.x.x
```
Then create a release on GitHub using this tag.
### Step 5) Upload wheels to PyPI
First, make sure you have the most recent version of twine installed on the host
```
python3 -m pip install --upgrade twine
```
Then, download and extract the wheels from the (successfully completed) workflow run. Place them inside the "dist" folder (create if it does not exist). Then, upload to PyPI with
```
python3 -m twine upload dist/*
```
#### Step 6) Tag Docker image with correct version
When pushing changes, a Docker image `lubo1994/mv-extractor:dev` is being build and pushed to DockerHub. Upon a release, this image should be tagged with the correct release version and the `latest` tag. To this end, first pull the `dev` image
```
docker pull lubo1994/mv-extractor:dev
```
and then login to the docker registry
```
cat docker_registry_password.txt | docker login --username --password-stdin
```
and tag and push the image as follows
```
docker tag lubo1994/mv-extractor:dev lubo1994/mv-extractor:vx.x.x
docker push lubo1994/mv-extractor:vx.x.x
docker tag lubo1994/mv-extractor:vx.x.x lubo1994/mv-extractor:latest
docker push lubo1994/mv-extractor:latest
```
where `vx.x.x` is replaced with the version of the release.
================================================
FILE: run.sh
================================================
#!/bin/bash
xhost +
docker run \
-it \
--ipc=host \
--env="DISPLAY" \
-v $(pwd):/home/video_cap \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
lubo1994/mv-extractor:latest \
"$@"
================================================
FILE: setup.py
================================================
from setuptools import find_packages, setup, Extension
import pkgconfig
from pathlib import Path
import numpy as np
pkgconfig_result = pkgconfig.parse('libavformat libswscale opencv4')
print("Numpy dir: ", np.get_include())
mvextractor = Extension('mvextractor.videocap',
include_dirs = [
*pkgconfig_result['include_dirs'],
np.get_include()
],
library_dirs = pkgconfig_result['library_dirs'],
libraries = pkgconfig_result['libraries'],
sources = [
'src/mvextractor/py_video_cap.cpp',
'src/mvextractor/video_cap.cpp',
'src/mvextractor/mat_to_ndarray.cpp'
],
extra_compile_args = ['-std=c++11'],
extra_link_args = ['-fPIC', '-Wl,-Bsymbolic'])
setup(
name='motion-vector-extractor',
author='Lukas Bommes',
author_email=' ',
version="2.0.0",
license='MIT',
url='https://github.com/LukasBommes/mv-extractor',
description=('Reads video frames and MPEG-4/H.264 motion vectors.'),
long_description=(Path(__file__).parent / "README.md").read_text(),
long_description_content_type='text/markdown',
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Environment :: X11 Applications",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Intended Audience :: Education",
"Intended Audience :: Information Technology",
"Topic :: Multimedia :: Video",
"Topic :: Multimedia :: Video :: Capture",
"Topic :: Multimedia :: Video :: Display",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: POSIX :: Linux",
"Programming Language :: C",
"Programming Language :: C++",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
],
keywords=['motion vector', 'video capture', 'mpeg4', 'h.264', 'compressed domain'],
ext_modules=[mvextractor],
packages=find_packages(where='src'),
package_dir={'': 'src'},
entry_points={
'console_scripts': [
'extract_mvs=mvextractor.__main__:main',
],
},
python_requires='>=3.9, <4',
# minimum versions of numpy and opencv are the oldest versions
# just supporting the minimum Python version (Python 3.9)
install_requires=['numpy>=1.19.3', 'opencv-python>=4.4.0.46']
)
================================================
FILE: src/mvextractor/__init__.py
================================================
================================================
FILE: src/mvextractor/__main__.py
================================================
import sys
import os
import time
from datetime import datetime
import argparse
import numpy as np
import cv2
from mvextractor.videocap import VideoCap
def draw_motion_vectors(frame, motion_vectors):
if len(motion_vectors) > 0:
num_mvs = np.shape(motion_vectors)[0]
shift = 2
factor = (1 << shift)
for mv in np.split(motion_vectors, num_mvs):
start_pt = (int((mv[0, 5] + mv[0, 7] / mv[0, 9]) * factor + 0.5), int((mv[0, 6] + mv[0, 8] / mv[0, 9]) * factor + 0.5))
end_pt = (mv[0, 5] * factor, mv[0, 6] * factor)
cv2.arrowedLine(frame, start_pt, end_pt, (0, 0, 255), 1, cv2.LINE_AA, shift, 0.1)
return frame
def main(args=None):
if args is None:
args = sys.argv[1:]
parser = argparse.ArgumentParser(description='Extract motion vectors from video.')
parser.add_argument('video_url', type=str, nargs='?', help='file path or url of the video stream')
parser.add_argument('-p', '--preview', action='store_true', help='show a preview video with overlaid motion vectors')
parser.add_argument('-v', '--verbose', action='store_true', help='show detailled text output')
parser.add_argument('-s', '--skip-decoding-frames', action='store_true', help='skip decoding RGB frames and return only motion vectors (faster)')
parser.add_argument('-d', '--dump', nargs='?', const=True,
help='dump frames, motion vectors and frame types to optionally specified output directory')
args = parser.parse_args()
if args.dump:
if isinstance(args.dump, str):
dumpdir = args.dump
else:
dumpdir = f"out-{datetime.now().strftime('%Y-%m-%dT%H:%M:%S')}"
for child in ["frames", "motion_vectors"]:
os.makedirs(os.path.join(dumpdir, child), exist_ok=True)
cap = VideoCap()
# open the video file
ret = cap.open(args.video_url)
if not ret:
raise RuntimeError(f"Could not open {args.video_url}")
if args.verbose:
print("Sucessfully opened video file")
if args.skip_decoding_frames:
cap.set_decode_frames(False)
step = 0
times = []
# continuously read and display video frames and motion vectors
while True:
if args.verbose:
print("Frame: ", step, end=" ")
tstart = time.perf_counter()
# read next video frame and corresponding motion vectors
ret, frame, motion_vectors, frame_type = cap.read()
tend = time.perf_counter()
telapsed = tend - tstart
times.append(telapsed)
# if there is an error reading the frame
if not ret:
if args.verbose:
print("No frame read. Stopping.")
break
# print results
if args.verbose:
print("frame type: {} | ".format(frame_type), end=" ")
if frame is not None:
print("frame size: {} | ".format(np.shape(frame)), end=" ")
else:
print("frame size: () | ", end=" ")
print("motion vectors: {} | ".format(np.shape(motion_vectors)), end=" ")
print("elapsed time: {} s".format(telapsed))
# draw vectors on frames
if not args.skip_decoding_frames and frame is not None:
frame = draw_motion_vectors(frame, motion_vectors)
# store motion vectors, frames, and fraem types in output directory
if args.dump:
np.save(os.path.join(dumpdir, "motion_vectors", f"mvs-{step}.npy"), motion_vectors)
with open(os.path.join(dumpdir, "frame_types.txt"), "a") as f:
f.write(frame_type+"\n")
if not args.skip_decoding_frames and frame is not None:
cv2.imwrite(os.path.join(dumpdir, "frames", f"frame-{step}.jpg"), frame)
step += 1
if args.preview and not args.skip_decoding_frames:
cv2.imshow("Frame", frame)
# if user presses "q" key stop program
if cv2.waitKey(1) & 0xFF == ord('q'):
break
if args.verbose:
print("average dt: ", np.mean(times))
cap.release()
# close the GUI window
if args.preview:
cv2.destroyAllWindows()
if __name__ == "__main__":
sys.exit(main())
================================================
FILE: src/mvextractor/mat_to_ndarray.cpp
================================================
// Taken from OpenCV master commit e2a5a6a05c7ce64911e1e898e986abe8dd26cab6
// File: opencv/modules/python/cv2.cpp
#include "mat_to_ndarray.hpp"
class PyAllowThreads
{
public:
PyAllowThreads() : _state(PyEval_SaveThread()) {}
~PyAllowThreads()
{
PyEval_RestoreThread(_state);
}
private:
PyThreadState* _state;
};
class PyEnsureGIL
{
public:
PyEnsureGIL() : _state(PyGILState_Ensure()) {}
~PyEnsureGIL()
{
PyGILState_Release(_state);
}
private:
PyGILState_STATE _state;
};
#define ERRWRAP2(expr) \
try \
{ \
PyAllowThreads allowThreads; \
expr; \
} \
catch (const cv::Exception &e) \
{ \
PyObject_SetAttrString(opencv_error, "file", PyString_FromString(e.file.c_str())); \
PyObject_SetAttrString(opencv_error, "func", PyString_FromString(e.func.c_str())); \
PyObject_SetAttrString(opencv_error, "line", PyInt_FromLong(e.line)); \
PyObject_SetAttrString(opencv_error, "code", PyInt_FromLong(e.code)); \
PyObject_SetAttrString(opencv_error, "msg", PyString_FromString(e.msg.c_str())); \
PyObject_SetAttrString(opencv_error, "err", PyString_FromString(e.err.c_str())); \
PyErr_SetString(opencv_error, e.what()); \
return 0; \
}
using namespace cv;
class NumpyAllocator : public MatAllocator
{
public:
NumpyAllocator() { stdAllocator = Mat::getStdAllocator(); }
~NumpyAllocator() {}
UMatData* allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const
{
UMatData* u = new UMatData(this);
u->data = u->origdata = (uchar*)PyArray_DATA((PyArrayObject*) o);
npy_intp* _strides = PyArray_STRIDES((PyArrayObject*) o);
for( int i = 0; i < dims - 1; i++ )
step[i] = (size_t)_strides[i];
step[dims-1] = CV_ELEM_SIZE(type);
u->size = sizes[0]*step[0];
u->userdata = o;
return u;
}
UMatData* allocate(int dims0, const int* sizes, int type, void* data, size_t* step, AccessFlag flags, UMatUsageFlags usageFlags) const CV_OVERRIDE
{
if( data != 0 )
{
// issue #6969: CV_Error(Error::StsAssert, "The data should normally be NULL!");
// probably this is safe to do in such extreme case
return stdAllocator->allocate(dims0, sizes, type, data, step, flags, usageFlags);
}
PyEnsureGIL gil;
int depth = CV_MAT_DEPTH(type);
int cn = CV_MAT_CN(type);
const int f = (int)(sizeof(size_t)/8);
int typenum = depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE :
depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT :
depth == CV_32S ? NPY_INT : depth == CV_32F ? NPY_FLOAT :
depth == CV_64F ? NPY_DOUBLE : f*NPY_ULONGLONG + (f^1)*NPY_UINT;
int i, dims = dims0;
cv::AutoBuffer _sizes(dims + 1);
for( i = 0; i < dims; i++ )
_sizes[i] = sizes[i];
if( cn > 1 )
_sizes[dims++] = cn;
PyObject* o = PyArray_SimpleNew(dims, _sizes.data(), typenum);
if(!o)
CV_Error_(Error::StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims));
return allocate(o, dims0, sizes, type, step);
}
bool allocate(UMatData* u, AccessFlag accessFlags, UMatUsageFlags usageFlags) const CV_OVERRIDE
{
return stdAllocator->allocate(u, accessFlags, usageFlags);
}
void deallocate(UMatData* u) const CV_OVERRIDE
{
if(!u)
return;
PyEnsureGIL gil;
CV_Assert(u->urefcount >= 0);
CV_Assert(u->refcount >= 0);
if(u->refcount == 0)
{
PyObject* o = (PyObject*)u->userdata;
Py_XDECREF(o);
delete u;
}
}
const MatAllocator* stdAllocator;
};
NumpyAllocator g_numpyAllocator;
int* NDArrayConverter::init() { import_array(); return NULL; }
NDArrayConverter::NDArrayConverter() { init(); }
PyObject* NDArrayConverter::toNDArray(const cv::Mat& m) {
if( !m.data )
Py_RETURN_NONE;
Mat temp, *p = (Mat*)&m;
if(!p->u || p->allocator != &g_numpyAllocator)
{
temp.allocator = &g_numpyAllocator;
ERRWRAP2(m.copyTo(temp));
p = &temp;
}
PyObject* o = (PyObject*)p->u->userdata;
Py_INCREF(o);
return o;
}
================================================
FILE: src/mvextractor/mat_to_ndarray.hpp
================================================
// Taken from OpenCV master commit e2a5a6a05c7ce64911e1e898e986abe8dd26cab6
// File: opencv/modules/python/cv2.cpp
#include
#include
#include
#include "opencv2/core/core.hpp"
#include "opencv2/core/types_c.h"
#include "opencv2/opencv_modules.hpp"
#include "pycompat.hpp"
#include