Ejemplo n.º 1
0
def run_window_config(config_cls: WindowConfig, timer=None, args=None) -> None:
    """
    Run an WindowConfig entering a blocking main loop

    Args:
        config_cls: The WindowConfig class to render
        args: Override sys.args
    """
    setup_basic_logging(config_cls.log_level)
    parser = create_parser()
    config_cls.add_arguments(parser)
    values = parse_args(args=args, parser=parser)
    config_cls.argv = values
    window_cls = get_local_window_cls(values.window)

    # Calculate window size
    size = values.size or config_cls.window_size
    size = int(size[0] * values.size_mult), int(size[1] * values.size_mult)

    # Resolve cursor
    show_cursor = values.cursor
    if show_cursor is None:
        show_cursor = config_cls.cursor

    window = window_cls(
        title=config_cls.title,
        size=size,
        fullscreen=config_cls.fullscreen or values.fullscreen,
        resizable=values.resizable
        if values.resizable is not None else config_cls.resizable,
        gl_version=config_cls.gl_version,
        aspect_ratio=config_cls.aspect_ratio,
        vsync=values.vsync if values.vsync is not None else config_cls.vsync,
        samples=values.samples
        if values.samples is not None else config_cls.samples,
        cursor=show_cursor if show_cursor is not None else True,
    )
    window.print_context_info()
    activate_context(window=window)
    timer = Timer()
    window.config = config_cls(ctx=window.ctx, wnd=window, timer=timer)

    timer.start()

    while not window.is_closing:
        current_time, delta = timer.next_frame()

        if window.config.clear_color is not None:
            window.clear(*window.config.clear_color)
        else:
            window.use()
        window.render(current_time, delta)
        window.swap_buffers()

    _, duration = timer.stop()
    window.destroy()
    if duration > 0:
        logger.info("Duration: {0:.2f}s @ {1:.2f} FPS".format(
            duration, window.frames / duration))
Ejemplo n.º 2
0
def run_window_config(config_cls: WindowConfig, timer=None, args=None) -> None:
    """
    Run an WindowConfig entering a blocking main loop

    Args:
        config_cls: The WindowConfig class to render
        args: Override sys.args
    """
    values = parse_args(args)
    window_cls = get_local_window_cls(values.window)

    window = window_cls(
        title=config_cls.title,
        size=config_cls.window_size,
        fullscreen=values.fullscreen,
        resizable=config_cls.resizable,
        gl_version=config_cls.gl_version,
        aspect_ratio=config_cls.aspect_ratio,
        vsync=values.vsync,
        samples=values.samples,
        cursor=values.cursor,
    )
    window.print_context_info()

    window.config = config_cls(ctx=window.ctx, wnd=window)

    timer = Timer()
    timer.start()

    while not window.is_closing:
        current_time, delta = timer.next_frame()

        window.ctx.screen.use()
        window.render(current_time, delta)
        window.swap_buffers()

    _, duration = timer.stop()
    window.destroy()
    print("Duration: {0:.2f}s @ {1:.2f} FPS".format(duration, window.frames / duration))
Ejemplo n.º 3
0
class VideoCapture:
    """
        ``VideoCapture`` it's an utility class to capture runtime render
        and save it as video. 
        
        Example:

        .. code:: python

            import moderngl_window
            from moderngl_window.capture import VideoCapture

            class CaptureTest(modenrgl_window.WindowConfig):
                
                def __init__(self, **kwargs):
                    super().__init__(**kwargs)
                    # do other initialization 

                    # define VideoCapture class
                    self.cap = VideoCapture()

                    # start recording
                    self.cap.start_capture(
                        filename="video.mp4",
                        target_fb = self.wnd.fbo
                    )
                
                def render(self, time, frametime):
                    # do other render stuff

                    # call record function after
                    self.cap.dump()
                
                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):

        self._ffmpeg = None
        self._video_timer = Timer()

        self._filename: str = None
        self._target_fb: moderngl.Framebuffer = None
        self._framerate: int = None

        self._last_frame = None
        self._recording = False
    
    @property
    def framerate(self) -> int:
        return self._framerate
    
    @framerate.setter
    def framerate(self, value: int):
        self._framerate = value
    
    @property
    def target_fb(self) -> moderngl.Framebuffer:
        return self._target_fb
    
    @target_fb.setter
    def target_fb(self, value: moderngl.Framebuffer):
        self._target_fb = value
    
    @property
    def filename(self) -> str:
        return self._filename
    
    @filename.setter
    def filename(self, value: str):
        self._filename = value
    

    def dump(self):
        """ Read data from the target framebuffer and dump the raw data 
            into ffmpeg stdin. 
            Call this function at the end of `render` function 
            
            Frame are saved respecting the video framerate.
        """
        if not self._recording:
            return
        
        # in theory to capture a frame at certain speed i'm testing if
        # the time passed after the last frame is at least dt = 1./target_fps .
        # This prevent the higher framerate during runtime to exceed the 
        # target framerate of the video. This doesn't work if runtime framerate 
        # is lower than target framerate.
        if  (self._video_timer.time - self._last_frame) >= 1./self._framerate:
            data = self._target_fb.read(components=3)
            self._ffmpeg.stdin.write(data)
            self._last_frame = self._video_timer.time


    def start(self, filename: str = None, target_fb: moderngl.Framebuffer = None, framerate=60):
        """
            Start ffmpeg pipe subprocess. 
            Call this at the end of __init__ function.

            Args:
                filename (str): name of the output file
                fb (moderngl.Framebuffer): target framebuffer to record
                framerate (int): framerate of the video

        """
        if not target_fb:
            raise Exception("target framebuffer can't be: None")
        else:
            self._target_fb = target_fb

        self._framerate = framerate
            
        if not filename:
            now = datetime.datetmie.now()
            filename = f'video_{now:%Y-%m-%d_%H:%M:%S.%f}.mp4'
        
        self._filename = filename
        
        width = target_fb.width
        height = target_fb.height

        # took from Wasaby2D project
        command = [
            'ffmpeg',
            '-y',  # (optional) overwrite output file if it exists
            '-f', 'rawvideo',
            '-vcodec', 'rawvideo',
            '-s', f'{width}x{height}',  # size of one frame
            '-pix_fmt', 'rgb24',
            '-r', f'{framerate}',  # frames per second
            '-i', '-',  # The imput comes from a pipe
            '-vf', 'vflip',
            '-an',  # Tells FFMPEG not to expect any audio
            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.")
            return

        self._video_timer.start()
        self._last_frame = self._video_timer.time

        self._recording = True
        print("Started video Recording")

    def release(self):
        """
        Stop the recording process
        """
        if self._recording:
            self._ffmpeg.stdin.close()
            ret = self._ffmpeg.wait()
            if ret == 0:
                print("Video saved succesfully")
            else:
                print("Error writing video.")
            self._recording = None
            self._video_timer.stop()
Ejemplo n.º 4
0
class BaseVideoCapture:
    """
        ``BaseVideoCapture`` is a base class to video capture

        Args:
            source (moderngl.Texture, moderngl.Framebuffer): the source of the capture
            framerate (int, float) : the framerate of the video, by thefault is 60 fps

        if the source is texture there are some requirements:
            - dtype = 'f1';
            - components >= 3.
    """
    def __init__(
        self,
        source: Union[moderngl.Texture, moderngl.Framebuffer] = None,
        framerate: Union[int, float] = 60,
    ):

        self._source = source
        self._framerate = framerate

        self._recording = False

        self._last_time: float = None
        self._filename: str = None
        self._width: int = None
        self._height: int = None

        self._timer = Timer()

        self._components: int = None  # for textures

        if isinstance(self._source, moderngl.Texture):
            self._components = self._source.components

    def _dump_frame(self, frame):
        """
            custom function called during self.save()

            Args:
                frame: frame data in bytes
        """
        raise NotImplementedError("override this function")

    def _start_func(self) -> bool:
        """
            custom function called during self.start_capture()

            must return a True if this function complete without errors
        """
        raise NotImplementedError("override this function")

    def _release_func(self):
        """
            custom function called during self.realease()
        """
        raise NotImplementedError("override this function")

    def _get_wh(self):
        """
            Return a tuple of the width and the height of the source
        """
        return self._source.width, self._source.height

    def _remove_file(self):
        """ Remove the filename of the video is it exist """
        if os.path.exists(self._filename):
            os.remove(self._filename)

    def start_capture(self,
                      filename: str = None,
                      framerate: Union[int, float] = 60):
        """
            Start the capturing process

            Args:
                filename (str): name of the output file
                framerate (int, float): framerate of the video

            if filename is not specified it will be generated based
            on the datetime.
        """
        if self._recording:
            print("Capturing is already started")
            return

        # ensure the texture has the correct dtype and components
        if isinstance(self._source, moderngl.Texture):
            if self._source.dtype != 'f1':
                print("source type: moderngl.Texture must be type `f1` ")
                return
            if self._components < 3:
                print(
                    "source type: moderngl.Texture must have at least 3 components"
                )
                return

        if not filename:
            now = datetime.datetime.now()
            filename = f'video_{now:%Y%m%d_%H%M%S}.mp4'

        self._filename = filename

        self._framerate = framerate
        self._width, self._height = self._get_wh()

        # if something goes wrong with the start
        # function, just stop and release the
        # capturing process
        if not self._start_func():
            self.release()
            print("Capturing failed")
            return

        self._timer.start()
        self._last_time = self._timer.time
        self._recording = True

    def save(self):
        """
            Save function to call at the end of render function
        """
        if not self._recording:
            return

        dt = 1. / self._framerate

        if self._timer.time - self._last_time > dt:

            # start counting
            self._last_time = self._timer.time

            if isinstance(self._source, moderngl.Framebuffer):
                # get data from framebuffer
                frame = self._source.read(components=3)
                self._dump_frame(frame)
            else:
                # get data from texture
                frame = self._source.read()
                self._dump_frame(frame)

    def release(self):
        """
        Stop the recording process
        """
        if self._recording:
            self._release_func()

            self._timer.stop()
            print(f"Video file succesfully saved as {self._filename}")
        self._recording = None