diff --git a/arcade/experimental/__init__.py b/arcade/experimental/__init__.py index 430fe91ff..3d20b5e15 100644 --- a/arcade/experimental/__init__.py +++ b/arcade/experimental/__init__.py @@ -1,6 +1,7 @@ """ Experimental stuff. API may change. """ +from .video_player import VideoPlayer from .texture_render_target import RenderTargetTexture from .shadertoy import Shadertoy, ShadertoyBuffer, ShadertoyBase from .crt_filter import CRTFilter @@ -8,6 +9,7 @@ __all__ = [ + "VideoPlayer", "RenderTargetTexture", "Shadertoy", "ShadertoyBuffer", diff --git a/arcade/experimental/shadertoy_video_cv2.py b/arcade/experimental/shadertoy_video_cv2.py index 9df13ce59..313bcb74f 100644 --- a/arcade/experimental/shadertoy_video_cv2.py +++ b/arcade/experimental/shadertoy_video_cv2.py @@ -14,12 +14,16 @@ SCREEN_TITLE = "ShaderToy Video" -class ShadertoyVideo(arcade.Window): +class ShadertoyVideo(arcade.View): + """ + Can be used to add effects like rain to the background of the game. + Make sure to inherit this view and call super for `__init__`, `on_draw`, `on_update` and `on_resize`. + """ - def __init__(self, width, height, title): - super().__init__(width, height, title, resizable=True) + def __init__(self, path: str): + super().__init__() self.shadertoy = Shadertoy( - self.get_framebuffer_size(), + self.window.get_framebuffer_size(), """ void mainImage( out vec4 fragColor, in vec2 fragCoord ) { @@ -37,18 +41,17 @@ def __init__(self, width, height, title): } """, ) - # INSERT YOUR OWN VIDEO HERE - self.video = cv2.VideoCapture("C:/Users/efors/Desktop/BigBuckBunny.mp4") + self.video = cv2.VideoCapture(str(arcade.resources.resolve_resource_path(path))) width, height = ( int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)), ) - self.video_texture = self.ctx.texture((width, height), components=3) - self.video_texture.wrap_x = self.ctx.CLAMP_TO_EDGE - self.video_texture.wrap_y = self.ctx.CLAMP_TO_EDGE + self.video_texture = self.window.ctx.texture((width, height), components=3) + self.video_texture.wrap_x = self.window.ctx.CLAMP_TO_EDGE + self.video_texture.wrap_y = self.window.ctx.CLAMP_TO_EDGE self.video_texture.swizzle = "BGR1" self.shadertoy.channel_0 = self.video_texture - self.set_size(width, height) + self.window.set_size(width, height) def on_draw(self): self.clear() @@ -60,15 +63,10 @@ def on_update(self, delta_time: float): def on_resize(self, width: int, height: int): super().on_resize(width, height) - self.shadertoy.resize(self.get_framebuffer_size()) + self.shadertoy.resize(self.window.get_framebuffer_size()) def next_frame(self): exists, frame = self.video.read() frame = cv2.flip(frame, 0) if exists: self.video_texture.write(frame) - - -if __name__ == "__main__": - ShadertoyVideo(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) - arcade.run() diff --git a/arcade/experimental/video_cv2.py b/arcade/experimental/video_cv2.py index 49cf9ecb4..d31cdd4da 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -9,15 +9,26 @@ pip install opencv-python """ from math import floor +from pathlib import Path +from typing import Union + import arcade from arcade.gl.geometry import quad_2d_fs import cv2 # type: ignore -class CV2Player(arcade.Window): +class VideoPlayerCV2: + """ + Primitive video player for arcade with cv2. + Renders to the entire screen. Use VideoPlayer to render to specific coordinate. + + :param path: Path of the video that is to be played. + """ + + def __init__(self, path: Union[str, Path]): + + self.ctx = arcade.get_window().ctx - def __init__(self): - super().__init__(800, 600, "OpenCV Video Player", resizable=True) self.quad_fs = quad_2d_fs() self.program = self.ctx.program( vertex_shader=""" @@ -44,40 +55,51 @@ def __init__(self): } """, ) + # Configure videoFrame sampler to read from texture channel 0 self.program["videoFrame"] = 0 - # Open the video (can also read from webcam) - self.video = cv2.VideoCapture("C:/Users/efors/Desktop/BigBuckBunny.mp4") + self.video = cv2.VideoCapture(str(arcade.resources.resolve_resource_path(path))) + # Query video size - width, height = ( + self._width, self._height = ( int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)), ) + # Get the framerate of the video self.video_frame_rate = self.video.get(cv2.CAP_PROP_FPS) # Keep track of the current frame and current time # to estimate a reasonable playback speed self.current_frame = 0 - self.time = 0 + self.time: float = 0.0 # Create and configure the OpenGL texture for the video - self.texture = self.ctx.texture((width, height), components=3) + self.texture = self.ctx.texture((self._width, self._height), components=3) # Swap the components in the texture because cv2 returns BGR data # Leave the alpha component as always 1 self.texture.swizzle = "BGR1" - # Change the window size to the video size - self.set_size(width, height) - def on_draw(self): - self.clear() + @property + def width(self): + """Video width.""" + return self._width + + @property + def height(self): + """Video height.""" + return self._height + + def draw(self): + """Call this in `on_draw`.""" - # Bind video texture to texture channel 0 + # Bind video texture to texture channel 0 self.texture.use(unit=0) # Draw a fullscreen quad using our texture program self.quad_fs.render(self.program) - def on_update(self, delta_time: float): + def update(self, delta_time): + """Move the frame forward.""" self.time += delta_time # Do we need to read a new frame? @@ -90,4 +112,25 @@ def on_update(self, delta_time: float): self.texture.write(frame) -CV2Player().run() +class CV2PlayerView(arcade.View): + def __init__(self, path: str): + super().__init__() + + self.video_player = VideoPlayerCV2(path) + + # Change the window size to the video size + self.window.set_size(self.video_player.width, self.video_player.height) + + def on_draw(self): + self.clear() + + self.video_player.draw() + + def on_update(self, delta_time: float): + self.video_player.update(delta_time) + + +if __name__ == '__main__': + window = arcade.Window(800, 600, "Video Player") + window.show_view(CV2PlayerView("/home/ibrahim/PycharmProjects/pyweek/35/Tetris-in-Ohio/assets/rain.mp4")) + window.run() diff --git a/arcade/experimental/video_player.py b/arcade/experimental/video_player.py index f75cd506d..dfa87e4c0 100644 --- a/arcade/experimental/video_player.py +++ b/arcade/experimental/video_player.py @@ -4,39 +4,63 @@ This requires that you have ffmpeg installed and you might need to tell pyglet where it's located. """ +from pathlib import Path +from typing import Optional, Tuple, Union + # import sys import pyglet import arcade -class VideoPlayer(arcade.Window): +class VideoPlayer: + """ + Primitive video player for arcade. - def __init__(self) -> None: - super().__init__(800, 600, "Video Player", resizable=True) + :param path: Path of the video that is to be played. + :param loop: Pass `True` to make the video loop. + """ + def __init__(self, path: Union[str, Path], loop=False): self.player = pyglet.media.Player() - # self.player.queue(pyglet.media.load("C:/Users/efors/Desktop/file_example_MP4_480_1_5MG.mp4")) - self.player.queue(pyglet.media.load("C:/Users/efors/Desktop/BigBuckBunny.mp4")) + self.player.loop = loop + self.player.queue(pyglet.media.load(str(arcade.resources.resolve_resource_path(path)))) self.player.play() - def on_draw(self): - self.clear() - # video_width, video_height = self.get_video_size() - # print((video_width, video_height), self.player.source.duration, self.player.time) + self.ctx = arcade.get_window().ctx + + self._width = arcade.get_window().width + self._height = arcade.get_window().height + + def draw(self, left: int = 0, bottom: int = 0, size: Optional[Tuple[int, int]] = None): + """ + Call this in `on_draw`. + + :param size: Pass None as one of the elements if you want to use the dimension(width, height) attribute. + """ + if size and len(size) == 2: + self._width = size[0] or self.width + self._height = size[1] or self.height with self.ctx.pyglet_rendering(): self.ctx.disable(self.ctx.BLEND) video_texture = self.player.texture if video_texture: video_texture.blit( - 0, - 0, + left, + bottom, width=self.width, height=self.height, ) - def on_update(self, delta_time: float): - pass + @property + def width(self): + """Video width.""" + return self._width + + @property + def height(self): + """Video height.""" + return self._height def get_video_size(self): if not self.player.source or not self.player.source.video_format: @@ -51,5 +75,19 @@ def get_video_size(self): return width, height -window = VideoPlayer() -arcade.run() +class VideoPlayerView(arcade.View): + def __init__(self, path) -> None: + super().__init__() + + self.video_player = VideoPlayer(path) + + def on_draw(self): + self.clear() + + self.video_player.draw() + + +if __name__ == '__main__': + window = arcade.Window(800, 600, "Video Player") + window.show_view(VideoPlayerView("/home/ibrahim/PycharmProjects/pyweek/35/Tetris-in-Ohio/assets/rain.mp4")) + window.run()