From d214a03b95a45f4c46e60cf977d87990fe801c25 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Tue, 28 Mar 2023 01:48:14 +0530 Subject: [PATCH 1/6] Made video players reusable by the user --- arcade/experimental/shadertoy_video_cv2.py | 21 ++++++++++---------- arcade/experimental/video_cv2.py | 21 +++++++++++--------- arcade/experimental/video_player.py | 23 +++++++++++----------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/arcade/experimental/shadertoy_video_cv2.py b/arcade/experimental/shadertoy_video_cv2.py index 9df13ce59..df9c45170 100644 --- a/arcade/experimental/shadertoy_video_cv2.py +++ b/arcade/experimental/shadertoy_video_cv2.py @@ -14,10 +14,14 @@ 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(), """ @@ -37,8 +41,10 @@ def __init__(self, width, height, title): } """, ) - # INSERT YOUR OWN VIDEO HERE - self.video = cv2.VideoCapture("C:/Users/efors/Desktop/BigBuckBunny.mp4") + # Used this because it will throw SIGSEGV when passed a Path like object, which is not very descriptive. + if not issubclass(type(path), str): + raise TypeError(f"The path is required to be a str object and not a {type(path)} object") + self.video = cv2.VideoCapture(path) width, height = ( int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)), @@ -67,8 +73,3 @@ def next_frame(self): 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..71c83f930 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -14,10 +14,14 @@ import cv2 # type: ignore -class CV2Player(arcade.Window): - - def __init__(self): - super().__init__(800, 600, "OpenCV Video Player", resizable=True) +class CV2Player(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` and `on_update`. + """ + + def __init__(self, path: str): + super().__init__() self.quad_fs = quad_2d_fs() self.program = self.ctx.program( vertex_shader=""" @@ -47,8 +51,10 @@ 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") + # Used this because it will throw SIGSEGV when passed a Path like object, which is not very descriptive. + if not issubclass(type(path), str): + raise TypeError(f"The path is required to be a str object and not a {type(path)} object") + self.video = cv2.VideoCapture(path) # Query video size width, height = ( int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH)), @@ -88,6 +94,3 @@ def on_update(self, delta_time: float): exists, frame = self.video.read() if exists: self.texture.write(frame) - - -CV2Player().run() diff --git a/arcade/experimental/video_player.py b/arcade/experimental/video_player.py index f75cd506d..0acdc6c2e 100644 --- a/arcade/experimental/video_player.py +++ b/arcade/experimental/video_player.py @@ -9,14 +9,20 @@ import arcade -class VideoPlayer(arcade.Window): +class VideoPlayer(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__` and `on_draw`. + """ - def __init__(self) -> None: - super().__init__(800, 600, "Video Player", resizable=True) + def __init__(self, path: str) -> None: + super().__init__() 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")) + # Used this because it will throw SIGSEGV when passed a Path like object, which is not very descriptive. + if not issubclass(type(path), str): + raise TypeError(f"The path is required to be a str object and not a {type(path)} object") + self.player.queue(pyglet.media.load(path)) self.player.play() def on_draw(self): @@ -35,9 +41,6 @@ def on_draw(self): height=self.height, ) - def on_update(self, delta_time: float): - pass - def get_video_size(self): if not self.player.source or not self.player.source.video_format: return 0, 0 @@ -49,7 +52,3 @@ def get_video_size(self): elif video_format.sample_aspect < 1: height /= video_format.sample_aspect return width, height - - -window = VideoPlayer() -arcade.run() From 4699cbebf8062b5253820468f322a2e0fd5ee5e7 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Tue, 28 Mar 2023 01:57:20 +0530 Subject: [PATCH 2/6] Transition from window to views --- arcade/experimental/shadertoy_video_cv2.py | 12 ++++++------ arcade/experimental/video_cv2.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/arcade/experimental/shadertoy_video_cv2.py b/arcade/experimental/shadertoy_video_cv2.py index df9c45170..67b2d0243 100644 --- a/arcade/experimental/shadertoy_video_cv2.py +++ b/arcade/experimental/shadertoy_video_cv2.py @@ -23,7 +23,7 @@ class ShadertoyVideo(arcade.View): 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 ) { @@ -49,12 +49,12 @@ def __init__(self, path: str): 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() @@ -66,7 +66,7 @@ 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() diff --git a/arcade/experimental/video_cv2.py b/arcade/experimental/video_cv2.py index 71c83f930..4488c3814 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -23,7 +23,7 @@ class CV2Player(arcade.View): def __init__(self, path: str): super().__init__() self.quad_fs = quad_2d_fs() - self.program = self.ctx.program( + self.program = self.window.ctx.program( vertex_shader=""" #version 330 @@ -65,15 +65,15 @@ def __init__(self, path: str): # 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.window.ctx.texture((width, 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) + self.window.set_size(width, height) def on_draw(self): self.clear() From cadb7c336255b05c33c69227376fff86301e4b52 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Tue, 28 Mar 2023 02:57:03 +0530 Subject: [PATCH 3/6] Added a separate class for VideoPlayer --- arcade/experimental/__init__.py | 2 + arcade/experimental/shadertoy_video_cv2.py | 5 +- arcade/experimental/video_cv2.py | 73 +++++++++++++++++----- arcade/experimental/video_player.py | 61 ++++++++++++------ 4 files changed, 103 insertions(+), 38 deletions(-) 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 67b2d0243..313bcb74f 100644 --- a/arcade/experimental/shadertoy_video_cv2.py +++ b/arcade/experimental/shadertoy_video_cv2.py @@ -41,10 +41,7 @@ def __init__(self, path: str): } """, ) - # Used this because it will throw SIGSEGV when passed a Path like object, which is not very descriptive. - if not issubclass(type(path), str): - raise TypeError(f"The path is required to be a str object and not a {type(path)} object") - self.video = cv2.VideoCapture(path) + 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)), diff --git a/arcade/experimental/video_cv2.py b/arcade/experimental/video_cv2.py index 4488c3814..e7102d2fe 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -14,16 +14,17 @@ import cv2 # type: ignore -class CV2Player(arcade.View): +class VideoPlayerCV2: """ - 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` and `on_update`. + Primitive video player for arcade with cv2. + Can be used to add effects like rain in the background. + + :param path: Path of the video that is to be played. """ - def __init__(self, path: str): - super().__init__() + def __init__(self, path: str, ctx: arcade.ArcadeContext): self.quad_fs = quad_2d_fs() - self.program = self.window.ctx.program( + self.program = ctx.program( vertex_shader=""" #version 330 @@ -48,18 +49,18 @@ def __init__(self, path: str): } """, ) + # Configure videoFrame sampler to read from texture channel 0 self.program["videoFrame"] = 0 - # Used this because it will throw SIGSEGV when passed a Path like object, which is not very descriptive. - if not issubclass(type(path), str): - raise TypeError(f"The path is required to be a str object and not a {type(path)} object") - self.video = cv2.VideoCapture(path) + self.video = cv2.VideoCapture(str(arcade.resources.resolve_resource_path(path))) + # Query video size width, 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 @@ -68,22 +69,33 @@ def __init__(self, path: str): self.time: float = 0.0 # Create and configure the OpenGL texture for the video - self.texture = self.window.ctx.texture((width, height), components=3) + self.texture = ctx.texture((width, 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.window.set_size(width, height) - def on_draw(self): - self.clear() + + @property + def width(self): + """Video width.""" + return int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH)) + + @property + def height(self): + """Video height.""" + return int(self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)) + + + def draw(self): + """Call this in `on_draw`.""" # 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? @@ -94,3 +106,32 @@ def on_update(self, delta_time: float): exists, frame = self.video.read() if exists: self.texture.write(frame) + + +class CV2PlayerView(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` and `on_update`. + """ + + def __init__(self, path: str): + super().__init__() + + self.video_player = VideoPlayerCV2(path, self.window.ctx) + + # 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 0acdc6c2e..e9930e4f8 100644 --- a/arcade/experimental/video_player.py +++ b/arcade/experimental/video_player.py @@ -4,41 +4,47 @@ 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 Union + # import sys import pyglet import arcade -class VideoPlayer(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__` and `on_draw`. +class VideoPlayer: """ + Primitive video player for arcade. + Can be used to add effects like rain in the background. - def __init__(self, path: str) -> None: - super().__init__() + :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() - # Used this because it will throw SIGSEGV when passed a Path like object, which is not very descriptive. - if not issubclass(type(path), str): - raise TypeError(f"The path is required to be a str object and not a {type(path)} object") - self.player.queue(pyglet.media.load(path)) + 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) - with self.ctx.pyglet_rendering(): - self.ctx.disable(self.ctx.BLEND) + def draw(self, ctx: arcade.ArcadeContext, width, height): + """ + Call this in `on_draw`. + + :param ctx: Pass arcade.Window.ctx as argument. + :param width: Width of the window. + :param height: Height of the window. + """ + with ctx.pyglet_rendering(): + ctx.disable(ctx.BLEND) video_texture = self.player.texture if video_texture: video_texture.blit( 0, 0, - width=self.width, - height=self.height, + width=width, + height=height, ) def get_video_size(self): @@ -52,3 +58,22 @@ def get_video_size(self): elif video_format.sample_aspect < 1: height /= video_format.sample_aspect return width, height + + +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(self.window.ctx, self.window.width, self.window.height) + + +if __name__ == '__main__': + window = arcade.Window(800, 600, "Video Player") + window.show_view(VideoPlayerView("/home/user/path/to/project/assets/rain.mp4")) + window.run() From ef6ea7ff1d149f9e2df85ebcd244781d63676406 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Tue, 28 Mar 2023 02:59:03 +0530 Subject: [PATCH 4/6] Fix type hint to include Path objects as well --- arcade/experimental/video_cv2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/experimental/video_cv2.py b/arcade/experimental/video_cv2.py index e7102d2fe..3a85c3b4a 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -22,7 +22,7 @@ class VideoPlayerCV2: :param path: Path of the video that is to be played. """ - def __init__(self, path: str, ctx: arcade.ArcadeContext): + def __init__(self, path: Union[str, Path], ctx: arcade.ArcadeContext): self.quad_fs = quad_2d_fs() self.program = ctx.program( vertex_shader=""" From 9b045653a487bca924c9efb36bbda2910f1471a8 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Tue, 28 Mar 2023 03:01:21 +0530 Subject: [PATCH 5/6] Add missing imports and change main path --- arcade/experimental/video_cv2.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arcade/experimental/video_cv2.py b/arcade/experimental/video_cv2.py index 3a85c3b4a..504b02d72 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -9,6 +9,9 @@ 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 @@ -133,5 +136,5 @@ def on_update(self, delta_time: float): 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.show_view(CV2PlayerView("/home/user/path/to/project/assets/rain.mp4")) window.run() From a9f5133090d122f77571a90f14558a427a1d8d5a Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Tue, 28 Mar 2023 16:48:24 +0530 Subject: [PATCH 6/6] Implement reviews --- arcade/experimental/video_cv2.py | 32 +++++++++----------- arcade/experimental/video_player.py | 46 +++++++++++++++++++---------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/arcade/experimental/video_cv2.py b/arcade/experimental/video_cv2.py index 504b02d72..d31cdd4da 100644 --- a/arcade/experimental/video_cv2.py +++ b/arcade/experimental/video_cv2.py @@ -20,14 +20,17 @@ class VideoPlayerCV2: """ Primitive video player for arcade with cv2. - Can be used to add effects like rain in the background. + 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], ctx: arcade.ArcadeContext): + def __init__(self, path: Union[str, Path]): + + self.ctx = arcade.get_window().ctx + self.quad_fs = quad_2d_fs() - self.program = ctx.program( + self.program = self.ctx.program( vertex_shader=""" #version 330 @@ -59,7 +62,7 @@ def __init__(self, path: Union[str, Path], ctx: arcade.ArcadeContext): 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)), ) @@ -72,27 +75,25 @@ def __init__(self, path: Union[str, Path], ctx: arcade.ArcadeContext): self.time: float = 0.0 # Create and configure the OpenGL texture for the video - self.texture = 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" - @property def width(self): """Video width.""" - return int(self.video.get(cv2.CAP_PROP_FRAME_WIDTH)) + return self._width @property def height(self): """Video height.""" - return int(self.video.get(cv2.CAP_PROP_FRAME_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) @@ -112,16 +113,11 @@ def update(self, delta_time): class CV2PlayerView(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` and `on_update`. - """ - def __init__(self, path: str): super().__init__() - self.video_player = VideoPlayerCV2(path, self.window.ctx) - + 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) @@ -136,5 +132,5 @@ def on_update(self, delta_time: float): if __name__ == '__main__': window = arcade.Window(800, 600, "Video Player") - window.show_view(CV2PlayerView("/home/user/path/to/project/assets/rain.mp4")) + 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 e9930e4f8..dfa87e4c0 100644 --- a/arcade/experimental/video_player.py +++ b/arcade/experimental/video_player.py @@ -5,7 +5,7 @@ and you might need to tell pyglet where it's located. """ from pathlib import Path -from typing import Union +from typing import Optional, Tuple, Union # import sys import pyglet @@ -15,7 +15,6 @@ class VideoPlayer: """ Primitive video player for arcade. - Can be used to add effects like rain in the background. :param path: Path of the video that is to be played. :param loop: Pass `True` to make the video loop. @@ -27,26 +26,42 @@ def __init__(self, path: Union[str, Path], loop=False): self.player.queue(pyglet.media.load(str(arcade.resources.resolve_resource_path(path)))) self.player.play() + self.ctx = arcade.get_window().ctx - def draw(self, ctx: arcade.ArcadeContext, width, height): + 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 ctx: Pass arcade.Window.ctx as argument. - :param width: Width of the window. - :param height: Height of the window. + + :param size: Pass None as one of the elements if you want to use the dimension(width, height) attribute. """ - with ctx.pyglet_rendering(): - ctx.disable(ctx.BLEND) + 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, - width=width, - height=height, + left, + bottom, + width=self.width, + height=self.height, ) + @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: return 0, 0 @@ -61,7 +76,6 @@ def get_video_size(self): class VideoPlayerView(arcade.View): - def __init__(self, path) -> None: super().__init__() @@ -70,10 +84,10 @@ def __init__(self, path) -> None: def on_draw(self): self.clear() - self.video_player.draw(self.window.ctx, self.window.width, self.window.height) + self.video_player.draw() if __name__ == '__main__': window = arcade.Window(800, 600, "Video Player") - window.show_view(VideoPlayerView("/home/user/path/to/project/assets/rain.mp4")) + window.show_view(VideoPlayerView("/home/ibrahim/PycharmProjects/pyweek/35/Tetris-in-Ohio/assets/rain.mp4")) window.run()