1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
|
from .base import BaseVideoCapture
import subprocess
import moderngl
class FFmpegCapture(BaseVideoCapture):
"""
``FFmpegCapture`` it's an utility class to capture runtime render
and save it as video.
Args:
Example:
.. code:: python
import moderngl_window
from moderngl_window.capture.ffmpeg import FFmpegCapture
class CaptureTest(modenrgl_window.WindowConfig):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# do other initialization
# define VideoCapture class
self.cap = FFmpegCapture(source=self.wnd.fbo)
# start recording
self.cap.start_capture(
filename="video.mp4",
framerate=30
)
def render(self, time, frametime):
# do other render stuff
# call record function after
self.cap.save()
def close(self):
# if realease func is not called during
# runtime. make sure to do it on the closing event
self.cap.release()
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._ffmpeg = None
def _start_func(self) -> bool:
"""
choose the right pixel format based on the number of components
and start a ffmper pipe with a subprocess.
"""
pix_fmt = 'rgb24' # 3 component, 1 byte per color -> 24 bit
# for the framebuffer is easier because i can read 3 component even if
# the color attachment has less components
if isinstance(self._source, moderngl.Texture) and self._components == 4:
pix_fmt = 'rgba' # 4 component , 1 byte per color -> 32 bit
command = [
'ffmpeg',
'-hide_banner',
'-loglevel', 'error', '-stats', # less verbose, only stats of recording
'-y', # (optional) overwrite output file if it exists
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-s', f'{self._width}x{self._height}', # size of one frame
'-pix_fmt', pix_fmt,
'-r', f'{self._framerate}', # frames per second
'-i', '-', # The imput comes from a pipe
'-vf', 'vflip',
'-an', # Tells FFMPEG not to expect any audio
self._filename,
]
# ffmpeg binary need to be on the PATH.
try:
self._ffmpeg = subprocess.Popen(
command,
stdin=subprocess.PIPE,
bufsize=0
)
except FileNotFoundError:
print("ffmpeg command not found. Be sure to add it to PATH")
return
return True
def _release_func(self):
"""
Safely release the capture
"""
self._ffmpeg.stdin.close()
_ = self._ffmpeg.wait()
def _dump_frame(self, frame):
"""
write the frame data in to the ffmpeg pipe
"""
self._ffmpeg.stdin.write(frame)
|