Skip to content

Commit f1082f1

Browse files
author
Ruben Cronie
committed
Initial commit
0 parents  commit f1082f1

8 files changed

+166
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
.python-version

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
Development
3+
================
4+
Install requirements:
5+
6+
pip install -r requirements.txt
7+
8+
Troubleshooting
9+
-------------------
10+
11+
If you get this error during installation:
12+
13+
ERROR: Could not build wheels for opencv-python which use PEP 517 and cannot be installed directly
14+
15+
Try this:
16+
17+
pip install --upgrade pip setuptools wheel

requirements.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
numpy
2+
opencv-python-headless>=4.5.5.62,<5.0.0
3+
pygobject==3.38.0
4+
ffmpeg-python
5+
black
6+
autoflake
7+
flake8
8+
psutil
9+
isort>=5.0.0,<6.0.0

setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
max-line-length=120

setup.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(
4+
name='vidutil',
5+
version='0.1.0',
6+
packages=find_packages(include=['vidutil', 'vidutil.*']),
7+
install_requires=[
8+
'numpy',
9+
'opencv-python-headless>=4.5.5.62,<5.0.0',
10+
'pygobject==3.38.0',
11+
'ffmpeg-python',
12+
'psutil',
13+
]
14+
)

vidutil/__init__.py

Whitespace-only changes.

vidutil/encoder.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import gc
2+
import logging
3+
import os
4+
import os.path
5+
from threading import Lock
6+
from typing import List
7+
8+
import ffmpeg
9+
import numpy as np
10+
from cv2 import cv2
11+
from memory import get_current_memory
12+
13+
# logging
14+
logger = logging.getLogger(__name__)
15+
logger.setLevel(logging.DEBUG)
16+
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
17+
handler = logging.StreamHandler()
18+
handler.setFormatter(formatter)
19+
logger.addHandler(handler)
20+
21+
22+
class VideoEncoder:
23+
"""
24+
This class exposes methods for opening, processing and saving a video.
25+
"""
26+
27+
lock = Lock()
28+
29+
@staticmethod
30+
def load(path) -> List:
31+
# todo:
32+
# - support multiple files
33+
# - support multiple processes
34+
# - support multiple threads
35+
# - support logging time
36+
logger.debug(f"Loading file://{os.path.abspath(path)}")
37+
frames = []
38+
cap = cv2.VideoCapture(path)
39+
ret = True
40+
while ret:
41+
# read one frame from the 'capture' object; img is (H, W, C) [height width channels?]
42+
ret, img = cap.read()
43+
if ret:
44+
frames.append(img)
45+
logger.debug(get_current_memory())
46+
gc.collect()
47+
logger.debug("VideoEncoder.load - garbage collected")
48+
return frames
49+
50+
@staticmethod
51+
def get_fps(path) -> float:
52+
# todo: verify it's not loaded into memory
53+
"""
54+
Return the FPS of a video file without loading it in to memory
55+
:param path:
56+
:return:
57+
"""
58+
return cv2.VideoCapture(path).get(cv2.CAP_PROP_FPS)
59+
60+
@staticmethod
61+
def get_total_frames(path) -> int:
62+
return int(cv2.VideoCapture(path).get(cv2.CAP_PROP_FRAME_COUNT))
63+
64+
@staticmethod
65+
def save(path: str, frames: List[np.array], fps: float, size: tuple) -> None:
66+
"""
67+
Save video to disk
68+
:param path: path to save image to
69+
:param frames: list of numpy arrays, each representing a single video frame
70+
:param size: a tuple (width, height)
71+
:param fps: int
72+
:return:
73+
"""
74+
# Options: mp4v, avc1
75+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
76+
video = cv2.VideoWriter(path, fourcc, fps, size)
77+
# todo: how to optimize? maybe by using separate file+thread per 10 seconds of video?
78+
length = len(frames)
79+
for i, frame in enumerate(frames):
80+
# todo: make percentage available to invoker of method
81+
percentage = round((i + 1) / length * 100, 2)
82+
logger.debug(percentage)
83+
video.write(frame)
84+
85+
video.release()
86+
87+
logger.info(get_current_memory())
88+
del video
89+
gc.collect()
90+
logger.info("VideoEncoder.save - garbage collected")
91+
logger.info(get_current_memory())
92+
93+
@staticmethod
94+
def merge_av(audio_path, video_path, output_path):
95+
if not audio_path:
96+
logger.info("no audio provided")
97+
if not video_path:
98+
logger.info("no video provided")
99+
return
100+
logger.debug(f"Merging audio and video: \n a: {audio_path} \n v: {video_path}")
101+
audio = ffmpeg.input(audio_path).audio
102+
video = ffmpeg.input(video_path).video
103+
out = ffmpeg.output(
104+
audio,
105+
video,
106+
output_path,
107+
vcodec="copy",
108+
acodec="aac",
109+
strict="experimental",
110+
)
111+
out.run()
112+
logger.info(f"saved to file://{os.path.abspath(output_path)}")
113+
114+
logger.info(get_current_memory())

vidutil/memory.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import os
2+
3+
import psutil
4+
5+
6+
def get_current_memory():
7+
process = psutil.Process(os.getpid())
8+
return process.memory_info().rss / 1024 ** 2

0 commit comments

Comments
 (0)