├── .gitignore ├── README.md ├── cvplayer ├── __init__.py ├── core │ ├── __init__.py │ ├── media_objects │ │ ├── __init__.py │ │ ├── image.py │ │ └── video.py │ ├── media_player │ │ ├── __init__.py │ │ ├── default.mp4 │ │ ├── image_player_core │ │ │ ├── back_end │ │ │ │ └── image_player.py │ │ │ └── interface │ │ │ │ ├── __init__.py │ │ │ │ ├── add_images_buttons.py │ │ │ │ ├── icons │ │ │ │ ├── ScreenShot.png │ │ │ │ ├── White.png │ │ │ │ ├── add.png │ │ │ │ ├── add_hover.png │ │ │ │ ├── add_pressed.png │ │ │ │ ├── black.png │ │ │ │ ├── cvplayerlogo.png │ │ │ │ ├── drop_down.png │ │ │ │ ├── next.png │ │ │ │ ├── next2.png │ │ │ │ ├── pause.png │ │ │ │ ├── play.png │ │ │ │ ├── previous.png │ │ │ │ ├── previous2.png │ │ │ │ └── solid-color-image.png │ │ │ │ ├── image_viewer.py │ │ │ │ ├── images_counter.py │ │ │ │ ├── images_list.py │ │ │ │ └── media_buttons.py │ │ ├── image_player_widget.py │ │ ├── video_player_core │ │ │ ├── __init__.py │ │ │ ├── back_end │ │ │ │ ├── video_player.py │ │ │ │ └── videos_manager.py │ │ │ └── interface │ │ │ │ ├── __init__.py │ │ │ │ ├── add_videos_button.py │ │ │ │ ├── icons │ │ │ │ ├── ScreenShot.png │ │ │ │ ├── White.png │ │ │ │ ├── add.png │ │ │ │ ├── add_hover.png │ │ │ │ ├── add_pressed.png │ │ │ │ ├── black.png │ │ │ │ ├── cvplayerlogo.png │ │ │ │ ├── drop_down.png │ │ │ │ ├── next.png │ │ │ │ ├── next2.png │ │ │ │ ├── pause.png │ │ │ │ ├── play.png │ │ │ │ ├── previous.png │ │ │ │ ├── previous2.png │ │ │ │ └── solid-color-image.png │ │ │ │ ├── media_buttons.py │ │ │ │ ├── print_screen_button.py │ │ │ │ ├── slider.py │ │ │ │ ├── video_speed_button.py │ │ │ │ ├── video_time_counter.py │ │ │ │ ├── videos_list.py │ │ │ │ └── viewer.py │ │ └── video_player_widget.py │ ├── references.txt │ ├── updater │ │ ├── hybrid_updater.py │ │ ├── old_video_updater.py │ │ ├── time_updater.py │ │ ├── updater.py │ │ └── video_updater.py │ └── utils │ │ ├── json_utils.py │ │ ├── layout_utils.py │ │ ├── stylesheet_utils.py │ │ ├── stylesheets │ │ ├── add_videos_button.css │ │ ├── image_counter.css │ │ ├── image_player_widget.css │ │ ├── image_viewer.css │ │ ├── next_frame_button.css │ │ ├── next_image_button.css │ │ ├── pause_button.css │ │ ├── play_button.css │ │ ├── previous_frame_button.css │ │ ├── previous_image_button.css │ │ ├── print_screen_button.css │ │ ├── video_player_widget.css │ │ ├── video_slider.css │ │ ├── video_speed_button.css │ │ ├── video_time_counter.css │ │ └── videos_list.css │ │ ├── time_and_date_utils.py │ │ ├── video_utils.py │ │ └── widgets_utils.py ├── docker │ ├── Dockerfile │ └── runing_docker.txt ├── icons │ ├── ScreenShot.png │ ├── White.png │ ├── add.png │ ├── add_hover.png │ ├── add_pressed.png │ ├── black.png │ ├── cvplayerlogo.png │ ├── drop_down.png │ ├── example.png │ ├── example2.mp4 │ ├── example2.png │ ├── example3.mp4 │ ├── example3.png │ ├── example4.png │ ├── example5.png │ ├── next.png │ ├── next2.png │ ├── pause.png │ ├── play.png │ ├── previous.png │ ├── previous2.png │ └── solid-color-image.png ├── image_player.py ├── stylesheets │ ├── add_videos_button.css │ ├── image_counter.css │ ├── image_player_widget.css │ ├── image_viewer.css │ ├── next_frame_button.css │ ├── next_image_button.css │ ├── pause_button.css │ ├── play_button.css │ ├── previous_frame_button.css │ ├── previous_image_button.css │ ├── print_screen_button.css │ ├── video_player_widget.css │ ├── video_slider.css │ ├── video_speed_button.css │ ├── video_time_counter.css │ └── videos_list.css └── video_player.py ├── demo_mmdetection.py ├── demo_yolov8.py ├── requirements.txt ├── setup.py └── tutorial.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .tox/ 29 | .coverage 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Rope 43 | .ropeproject 44 | 45 | # Django stuff: 46 | *.log 47 | *.pot 48 | 49 | # Sphinx documentation 50 | docs/_build/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
cvplayer 3 |

4 | 5 |

6 | Universal media player for computer vision models and their inferences 7 |

8 |
9 | 10 | 11 | Examples of the video and image player with detection and segmentation models 12 | 13 | ## Description 14 | 15 | Welcome to **CVPlayer**, a powerful and easy to use tool for visualizing computer vision models inferences. 16 | Tired of writing long codes just to visualize your model's output? With CVPlayer, you can easily analyze the output of your models with just a few lines of code, no mather wich library or framework you are using, saving you from long codes for a simple inference visualization and ofering you an a lot better experience over the traditional way of doing it. 17 | 18 |

19 | 20 |

21 | 22 | 23 | ## Features 24 | - [x] Video inference visualization. 25 | - [x] Images inference visualization. 26 | - [x] Compatible with any computer vision framework. 27 | - [x] Frame level control. 28 | - [x] Easy to use. 29 | - [ ] Portable for PyQt6 aplications. 30 | - [ ] Manipulate the player by an internal API. 31 | 32 | 33 | ## Requirements 34 | 35 | ### pip 36 | 37 | ```bash 38 | # inside your env 39 | pip install PyQt6==6.4.2 PyQt6-Qt6==6.4.2 PyQt6-sip==13.4.1 40 | pip install opencv-python 41 | pip install pillow 42 | ``` 43 | 44 | ## Instalation 45 | 46 | ```bash 47 | # inside your env 48 | git clone https://github.com/edu010101/cvplayer 49 | cd cvplayer 50 | pip install -e . 51 | ``` 52 | ## Usage 53 | 54 | ### Tutorials 55 | A colab tutorial for getting started with cvplayer [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](tutorial.ipynb) 56 | 57 | ### Controls 58 | - Play/Pause: Space Bar (⎵). 59 | - Next Frame/Image: Right Key (→). 60 | - Previous Frame/Image: Left Key (←). 61 | - Change Video Speed: Up and Down Keys (↑,↓). 62 | 63 | ### Video Player 64 | 65 | ```python 66 | from cvplayer import VideoPlayer 67 | 68 | #create a class with any name 69 | class ExampleName(): 70 | def __init___(self): #it can have many args as you need 71 | #initialize your model 72 | self.model = 'path/to/your/model' 73 | 74 | #your class NEED to have this method with this exactly name. 75 | def custom_method(self, numpy_image): #this method can have just the np.array parameter 76 | #this method receives an np.array representing your image or frame 77 | #then modify and inference your array as you want 78 | 79 | return numpy_image #then return your modified np.array 80 | 81 | VideoPlayer(ExampleName()) #load your class as an argument to the player 82 | ``` 83 | 84 | ### Yolov8 demo 85 | 86 | ```python 87 | from cvplayer import VideoPlayer 88 | from ultralytics import YOLO 89 | import cv2 90 | 91 | #yolov8 example 92 | class Yolov8(): 93 | def __init__(self) -> None: 94 | self.model = YOLO("yolov8n.pt") # load a pretrained model 95 | 96 | def custom_method(self, image): #method to be called on each frame and do whatever you want 97 | results = self.model(image) # predict on an image 98 | for result in results: 99 | boxes = result.boxes # Boxes object for bbox outputs 100 | for box in boxes: 101 | cv2.rectangle(image, (int(box.xyxy[0][0]), int(box.xyxy[0][1])), (int(box.xyxy[0][2]), int(box.xyxy[0][3])), (0, 255, 0), 2) 102 | 103 | return image #return the image with the changes 104 | 105 | VideoPlayer(Yolov8()) #pass the class to the VideoPlayer and start the player 106 | ``` 107 | 108 | ### Image Player 109 | 110 | ```python 111 | from cvplayer import ImagePlayer 112 | 113 | #create a class with any name 114 | class ExampleClass(): 115 | def __init___(self): #it can have many args as you need 116 | #initialize your model 117 | self.model = 'path/to/your/model' 118 | 119 | #your class NEED to have this method with this exactly name. 120 | def custom_method(self, numpy_image): 121 | #this method receives an np.array representing your image or frame 122 | #modify and inference your array as you want 123 | 124 | return numpy_image #then return your modified np.array 125 | 126 | imageclassifier = ExampleClass() 127 | ImagePlayer(imageclassifier) #load your class as an argument to the player 128 | ``` 129 | 130 | ### mmdetection demo 131 | 132 | ```python 133 | from cvplayer import ImagePlayer 134 | from mmdet.apis import inference_detector, init_detector 135 | 136 | #mmdectection example 137 | class FasterRCNN(): 138 | def __init__(self) -> None: #always load the model in the constructor 139 | model_config='your_path_to_config/faster_rcnn_r50_fpn_1x.py' #you can use any model from mmdetection 140 | model_weights='your_path_to_weights/epoch_50.pth' 141 | self.detection_model = init_detector(model_config, model_weights, device='cuda:0') 142 | 143 | def custom_method(self, numpy_image): 144 | detection_result = inference_detector(self.detection_model, numpy_image) 145 | return self.detection_model.show_result(numpy_image, detection_result, score_thr=0.7, show=False) 146 | 147 | ImagePlayer(FasterRCNN()) #pass the class to the ImagePlayer and start the player 148 | 149 | ``` 150 | 151 | -------------------------------------------------------------------------------- /cvplayer/__init__.py: -------------------------------------------------------------------------------- 1 | from cvplayer.video_player import VideoPlayer 2 | from cvplayer.image_player import ImagePlayer -------------------------------------------------------------------------------- /cvplayer/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/__init__.py -------------------------------------------------------------------------------- /cvplayer/core/media_objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_objects/__init__.py -------------------------------------------------------------------------------- /cvplayer/core/media_objects/image.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | 3 | class Image(): 4 | def __init__(self, image_path:str) -> None: 5 | self.image = cv2.imread(image_path) 6 | 7 | def get_image(self): 8 | return self.image 9 | -------------------------------------------------------------------------------- /cvplayer/core/media_objects/video.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | from cvplayer.core.utils.video_utils import calculate_time_beetwen_frames 3 | 4 | MINIMAL_FLOAT = 0.0000001 5 | 6 | class Video: 7 | current_frame = None 8 | current_frame_id = 0 9 | current_milliseconds = 0 10 | current_seconds = 0 11 | future_frame_id = 0 12 | 13 | def __init__(self, video_path: str, frames_to_jump: int = 1) -> None: 14 | self.video = cv2.VideoCapture(video_path) 15 | self.frames_to_jump = frames_to_jump 16 | self.fps = self.get_video_fps() 17 | self.total_number_of_frames = self.get_total_number_of_frames() 18 | 19 | def set_current_frame_id(self, frame_id: int) -> None: 20 | self.future_frame_id = frame_id 21 | 22 | def set_current_frame_position_by_index(self) -> None: 23 | self.video.set(cv2.CAP_PROP_POS_FRAMES, self.future_frame_id) 24 | 25 | def set_frames_to_jump(self, frames_to_jump: int) -> None: 26 | self.frames_to_jump = frames_to_jump 27 | 28 | def grab_encoded_next_frame(self) -> None: 29 | self.video.grab() 30 | 31 | def decode_current_frame(self) -> None: 32 | _, self.current_frame = self.video.retrieve() 33 | 34 | def update_to_next_frame(self) -> None: 35 | _, self.current_frame = self.video.read() 36 | 37 | def update_current_frame_id(self) -> None: 38 | self.current_frame_id = self.get_current_frame_id() 39 | 40 | def update_video_time(self) -> None: 41 | self.current_milliseconds = self.get_milliseconds() 42 | self.current_seconds = self.get_seconds() 43 | 44 | def update_to_next_frame_based_on_frames_to_jump(self) -> None: 45 | for _ in range(self.frames_to_jump): 46 | self.grab_encoded_next_frame() 47 | 48 | self.decode_current_frame() 49 | 50 | #Basic a union of the last 3 methods 51 | def update_to_next_video_moment(self) -> None: 52 | """Iterates to the next frame and updates the video parameters""" 53 | if self.current_frame_id + self.frames_to_jump >= self.total_number_of_frames: 54 | self.set_current_frame_id(0) 55 | self.update_to_specific_video_moment() 56 | 57 | self.update_to_next_frame_based_on_frames_to_jump() 58 | self.update_current_frame_id() 59 | self.update_video_time() 60 | # print('Current frame id: ', self.current_frame_id) 61 | 62 | def update_to_specific_video_moment(self) -> None: 63 | """Iterates to a specifc frame and updates the video parameters""" 64 | self.set_current_frame_position_by_index() 65 | self.decode_current_frame() 66 | self.update_current_frame_id() 67 | self.update_video_time() 68 | 69 | def get_milliseconds(self) -> int: 70 | return (self.current_frame_id / self.fps) * 1000 71 | 72 | def get_seconds(self) -> float: 73 | return self.current_frame_id / self.fps 74 | 75 | def get_current_frame_id(self) -> int: 76 | return int(self.video.get(cv2.CAP_PROP_POS_FRAMES)) 77 | 78 | def get_total_number_of_frames(self) -> int: 79 | return self.video.get(cv2.CAP_PROP_FRAME_COUNT) 80 | 81 | def get_video_fps(self) -> float: 82 | return self.video.get(cv2.CAP_PROP_FPS) 83 | 84 | def get_time_between_frames(self) -> float: 85 | return calculate_time_beetwen_frames(self.fps) 86 | 87 | def close_video(self) -> None: 88 | self.video.release() -------------------------------------------------------------------------------- /cvplayer/core/media_player/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/__init__.py -------------------------------------------------------------------------------- /cvplayer/core/media_player/default.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/default.mp4 -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/back_end/image_player.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import os 3 | import numpy as np 4 | class ImagePlayer(): 5 | def __init__(self, image_viewer, custom_method) -> None: 6 | #it eill start the viewer, and then loads an default image on it 7 | #after that you will be able to change the image 8 | self.image_viewer = image_viewer 9 | self.custom_method = custom_method 10 | self.image = None 11 | 12 | def set_image(self, image_path): 13 | if os.path.splitext(image_path)[1] == '.npy': 14 | self.image = np.load(image_path) 15 | else: 16 | self.image = cv2.imread(image_path) 17 | self.image = self.check_image(self.image) 18 | self.image_viewer.show_cv2_image(self.custom_method(self.image)) 19 | 20 | def check_image(self, image): 21 | w, h, c = image.shape 22 | if image is None or w == 0 or h == 0 or c == 0: 23 | raise ValueError("Image is not valid") 24 | if c > 3: 25 | image = image[:, :, :3] 26 | image = image.astype(np.uint8) 27 | print("Image has more than 3 channels, it will be converted to 3 channels") 28 | return image 29 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/__init__.py: -------------------------------------------------------------------------------- 1 | from .add_images_buttons import AddImagesButton 2 | from .images_counter import ImageCounter 3 | from .image_viewer import ImageViewer 4 | from .images_list import ImagesList 5 | from .media_buttons import NextImageButton, PreviousImageButton 6 | 7 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/add_images_buttons.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QPushButton, QFileDialog 2 | from cvplayer.core.utils.widgets_utils import start_widget_basics 3 | from pkg_resources import resource_filename 4 | 5 | class AddImagesButton(QPushButton): 6 | def __init__(self, images_list, layout=None): 7 | super().__init__() 8 | start_widget_basics(self, layout) 9 | self.set_css() 10 | self.images_list = images_list 11 | self.clicked.connect(self.get_images) 12 | 13 | def get_images(self): 14 | images_files = QFileDialog.getOpenFileNames(self, "Select Images", "", "Image Files (*.jpg *.png *.jpeg *.JPG *.PNG *.JPEG *.tif *.tiff *.npy)")[0] 15 | if len(images_files) > 0: 16 | self.images_list.add_images(images_files) 17 | 18 | def set_css(self): 19 | add_icon_path = resource_filename(__name__, 'icons/add.png').replace("\\", "/") 20 | hover_icon_path = resource_filename(__name__, 'icons/add_hover.png').replace("\\", "/") 21 | pressed_icon_path = resource_filename(__name__, 'icons/add_pressed.png').replace("\\", "/") 22 | css_str = f""" 23 | QPushButton {{ 24 | background-color: transparent; 25 | border-radius: 10px; 26 | border-image: url({add_icon_path}); 27 | max-width: 50px; 28 | max-height: 50px; 29 | min-width: 50px; 30 | min-height: 50px; 31 | margin: 0%; 32 | padding: 0%; 33 | border: 0px; 34 | }} 35 | QPushButton:hover {{ 36 | border-image: url({hover_icon_path}); 37 | }} 38 | QPushButton:pressed {{ 39 | border-image: url({pressed_icon_path}); 40 | }} 41 | """ 42 | self.setStyleSheet(css_str) 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/ScreenShot.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/White.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/add.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/add_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/add_hover.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/add_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/add_pressed.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/black.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/cvplayerlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/cvplayerlogo.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/drop_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/drop_down.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/next.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/next2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/next2.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/pause.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/play.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/previous.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/previous2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/previous2.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/icons/solid-color-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/image_player_core/interface/icons/solid-color-image.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/image_viewer.py: -------------------------------------------------------------------------------- 1 | from PyQt6 import QtCore, QtGui, QtWidgets 2 | import cvplayer.core.utils.widgets_utils as widgets_utils 3 | import cvplayer.core.utils.video_utils as video_utils 4 | import cv2 5 | from pkg_resources import resource_filename 6 | 7 | class ImageViewer(QtWidgets.QGraphicsView): 8 | photoClicked = QtCore.pyqtSignal(QtCore.QPoint) 9 | change_image_on_viewer = QtCore.pyqtSignal() 10 | 11 | def __init__(self, layout=None, CSS=None): 12 | super(ImageViewer, self).__init__() 13 | widgets_utils.start_widget_basics(self, layout, CSS) 14 | self.cv2_image = None 15 | self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 16 | self._zoom = 0 17 | self._empty = True 18 | self._scene = QtWidgets.QGraphicsScene(self) 19 | self._photo = QtWidgets.QGraphicsPixmapItem() 20 | self._scene.addItem(self._photo) 21 | self.setScene(self._scene) 22 | self.setTransformationAnchor(QtWidgets.QGraphicsView.ViewportAnchor.AnchorUnderMouse) 23 | self.setResizeAnchor(QtWidgets.QGraphicsView.ViewportAnchor.AnchorUnderMouse) 24 | self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) 25 | self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) 26 | self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30))) 27 | self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame ) 28 | self.change_image_on_viewer.connect(self.setPhoto) 29 | self.show_cv2_image(cv2.imread(resource_filename(__name__, 'icons/black.png'))) 30 | 31 | def show_cv2_image(self, cv2_image): 32 | self.QPixmap = video_utils.cv2_image_to_QPixmap(cv2_image) 33 | self.change_image_on_viewer.emit() 34 | 35 | def hasPhoto(self): 36 | return not self._empty 37 | 38 | def fitInView(self, scale=True): 39 | rect = QtCore.QRectF(self._photo.pixmap().rect()) 40 | self.setSceneRect(rect) 41 | unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1)) 42 | self.scale(1 / unity.width(), 1 / unity.height()) 43 | viewrect = self.viewport().rect() 44 | scenerect = self.transform().mapRect(rect) 45 | factor = min(viewrect.width() / scenerect.width(), 46 | viewrect.height() / scenerect.height()) 47 | self.scale(factor, factor) 48 | self._zoom = 0 49 | 50 | def setPhoto(self): 51 | self._zoom = 0 52 | self._empty = False 53 | self.setDragMode(QtWidgets.QGraphicsView.DragMode.ScrollHandDrag) 54 | self._photo.setPixmap(self.QPixmap) 55 | self.fitInView() 56 | 57 | def wheelEvent(self, event): 58 | if self.hasPhoto(): 59 | if event.angleDelta().y() > 0: 60 | factor = 1.25 61 | self._zoom += 1 62 | else: 63 | factor = 0.8 64 | self._zoom -= 1 65 | if self._zoom > 0: 66 | self.scale(factor, factor) 67 | elif self._zoom == 0: 68 | self.fitInView() 69 | else: 70 | self._zoom = 0 71 | 72 | def toggleDragMode(self): 73 | if self.dragMode() == QtWidgets.QGraphicsView.DragMode.ScrollHandDrag: 74 | self.setDragMode(QtWidgets.QGraphicsView.DragMode.NoDrag) 75 | elif not self._photo.pixmap().isNull(): 76 | self.setDragMode(QtWidgets.QGraphicsView.DragMode.ScrollHandDrag) 77 | 78 | def mousePressEvent(self, event): 79 | if self._photo.isUnderMouse(): 80 | self.photoClicked.emit(self.mapToScene(event.pos()).toPoint()) 81 | super(ImageViewer, self).mousePressEvent(event) 82 | 83 | 84 | 85 | # class ImageViewer(QtWidgets.QLabel): 86 | # def __init__(self, layout=None, CSS='cvplayer/stylesheets/image_viewer.css'): 87 | # super().__init__() 88 | # self.setScaledContents(True) 89 | # widgets_utils.start_widget_basics(self, layout, CSS) 90 | # self.cv2_image = cv2.imread('/home/eduardo/Pictures/Screenshot from 2022-10-02 10-54-40.png') 91 | # self.show_cv2_image(self.cv2_image) 92 | 93 | # def show_cv2_image(self, cv2_image): 94 | # QPixmap = video_utils.cv2_image_to_QPixmap(cv2_image) 95 | 96 | # w= self.width() 97 | # h= self.height() 98 | 99 | # scaled_pixmap = QPixmap#.scaled(w,h, QtCore.Qt.AspectRatioMode.KeepAspectRatio) 100 | 101 | # self.setPixmap(scaled_pixmap) 102 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/images_counter.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QLabel 2 | from cvplayer.core.utils.widgets_utils import start_widget_basics 3 | 4 | class ImageCounter(QLabel): 5 | def __init__(self, images_list, layout, CSS = 'stylesheets/image_counter.css', X=40, Y=40): 6 | super().__init__() 7 | self.images_list = images_list 8 | start_widget_basics(self, layout, CSS)#, fixed_width=X,fixed_height=Y) 9 | self.set_image_counter() 10 | self.images_list.currentIndexChanged.connect(self.set_image_counter) 11 | self.images_list.image_added.connect(self.set_image_counter) 12 | 13 | def set_image_counter(self): 14 | self.setText(f'{self.images_list.currentIndex() + 1}/{self.images_list.count()}') -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/images_list.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QComboBox 2 | from PyQt6.QtCore import pyqtSignal, Qt 3 | from cvplayer.core.utils.widgets_utils import start_widget_basics 4 | import os 5 | from pkg_resources import resource_filename 6 | 7 | class ImagesList(QComboBox): 8 | images_dict = {} 9 | image_added = pyqtSignal() 10 | current_image_path = None 11 | image_player = None 12 | 13 | def __init__(self, image_player, layout,X=350,Y=40) -> None: 14 | super().__init__() 15 | start_widget_basics(self, layout, fixed_height=Y) 16 | self.set_css() 17 | self.image_player = image_player 18 | self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) 19 | self.setMinimumWidth(X) 20 | self.currentIndexChanged.connect(self.set_current_image) 21 | 22 | def set_current_image(self, image_index): 23 | self.current_image_path = self.images_dict[self.currentText()] 24 | self.image_player.set_image(self.current_image_path) 25 | 26 | def add_images(self, images_path): 27 | for image_path in images_path: 28 | self.check_video_path(image_path) 29 | image_name = os.path.basename(image_path) 30 | self.images_dict[image_name] = image_path 31 | self.addItem(image_name) 32 | self.image_added.emit() 33 | #self.setCurrentIndex(-1) 34 | 35 | def check_video_path(self, video_path): 36 | if not os.path.exists(video_path): 37 | raise FileNotFoundError("Image path not found") 38 | elif os.path.isfile(video_path): 39 | return True 40 | 41 | def set_css(self): 42 | css_str = """ 43 | QComboBox { 44 | border-radius: 7px; 45 | color: lightgray; 46 | background-color: #333333; 47 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 48 | min-width: 350px; 49 | max-width: 350px; 50 | } 51 | QComboBox::hover{ 52 | 53 | background-color: #4e5050; 54 | border-radius: 5px; 55 | } 56 | QComboBox:!editable:on, QComboBox::drop-down:editable:on { 57 | background: #4e5050; 58 | } 59 | QComboBox::drop-down { 60 | subcontrol-origin: padding; 61 | subcontrol-position: top right; 62 | width: 30px; 63 | border-bottom-right-radius: 3px; 64 | border-radius: 5px; 65 | } 66 | QComboBox::down-arrow { 67 | image: url(""" + resource_filename(__name__, 'icons/drop_down.png').replace("\\", "/") + """); 68 | height: 15px; 69 | width: 15px; 70 | } 71 | QComboBox QAbstractItemView { 72 | border-radius: 5px; 73 | selection-color: #4e5050; 74 | selection-background-color: #4e5050; 75 | color: rgba(91,91,91,255); 76 | background-color: #4e5050; 77 | outline: 0px; 78 | font-size: 12pt; 79 | font-weight: 400; 80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 81 | } 82 | QListView::item { 83 | height:40px; 84 | outline: 0px; 85 | } 86 | QListView::item:selected { 87 | background-color: #4e5050; 88 | outline: 0px; 89 | } 90 | QScrollBar{ 91 | width:0px; 92 | }""" 93 | self.setStyleSheet(css_str) -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_core/interface/media_buttons.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QPushButton 2 | import cvplayer.core.utils.widgets_utils as widgets_utils 3 | from PyQt6.QtGui import QKeySequence 4 | from pkg_resources import resource_filename 5 | class NextImageButton(QPushButton): 6 | def __init__(self, images_list, layout, X=40, Y=40): 7 | super().__init__() 8 | self.setShortcut(QKeySequence('Right')) 9 | widgets_utils.start_widget_basics(self, layout, fixed_width=X,fixed_height=Y) 10 | self.set_css() 11 | self.images_list = images_list 12 | self.pressed.connect(self.next_image) 13 | 14 | def next_image(self): 15 | if self.images_list.currentIndex() + 1 < self.images_list.count(): 16 | self.images_list.setCurrentIndex(self.images_list.currentIndex() + 1) 17 | 18 | def set_css(self): 19 | css_str = """ 20 | QPushButton{ 21 | border-image: url("""+ resource_filename(__name__, 'icons/next2.png').replace("\\", "/") +"""); 22 | background:transparent; 23 | border-radius: 10px; 24 | border: none; 25 | outline: none; 26 | } 27 | QPushButton:hover { 28 | background-color: rgba(206, 197, 197, 0.21); 29 | } 30 | QPushButton:pressed{ 31 | background-color: rgba(132, 129, 129, 0.264); 32 | } 33 | """ 34 | self.setStyleSheet(css_str) 35 | 36 | class PreviousImageButton(QPushButton): 37 | def __init__(self, images_list, layout, X=40, Y=40): 38 | super().__init__() 39 | self.setShortcut(QKeySequence('Left')) 40 | widgets_utils.start_widget_basics(self, layout, fixed_width=X,fixed_height=Y) 41 | self.set_css() 42 | self.images_list = images_list 43 | self.pressed.connect(self.previous_image) 44 | 45 | def previous_image(self): 46 | if self.images_list.currentIndex() >= 1: 47 | self.images_list.setCurrentIndex(self.images_list.currentIndex() - 1) 48 | 49 | def set_css(self): 50 | css_str = """ 51 | QPushButton{ 52 | border-image: url("""+ resource_filename(__name__, 'icons/previous2.png').replace("\\", "/") +"""); 53 | background:transparent; 54 | border-radius: 10px; 55 | border: none; 56 | outline: none; 57 | } 58 | QPushButton:hover { 59 | background-color: rgba(206, 197, 197, 0.21); 60 | } 61 | QPushButton:pressed{ 62 | background-color: rgba(132, 129, 129, 0.264); 63 | } 64 | """ 65 | self.setStyleSheet(css_str) 66 | 67 | 68 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/image_player_widget.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy 2 | from PyQt6.QtCore import Qt 3 | from cvplayer.core.media_player.image_player_core.interface import * 4 | from cvplayer.core.media_player.image_player_core.back_end.image_player import ImagePlayer 5 | from cvplayer.core.utils.widgets_utils import start_widget_basics 6 | import cv2 7 | 8 | class ImagePlayerWidget(QWidget): 9 | def __init__(self, custom_class) -> None: 10 | super().__init__() 11 | start_widget_basics(self, None, 'stylesheets/image_player_widget.css', minimum_height=300, minimum_width=300) 12 | self.custom_class = custom_class 13 | self.image_viewer = ImageViewer() 14 | self.image_player = ImagePlayer(self.image_viewer, self.custom_class.custom_method) 15 | self.build_ui_elements() 16 | 17 | def build_ui_elements(self): 18 | self.main_layout = QVBoxLayout(self) 19 | self.top_layout = QHBoxLayout() 20 | 21 | self.main_layout.addLayout(self.top_layout) 22 | self.main_layout.addWidget(self.image_viewer)#, alignment= Qt.AlignmentFlag.AlignCenter) 23 | 24 | self.images_list = ImagesList(self.image_player, self.top_layout) 25 | self.top_layout.addSpacerItem(QSpacerItem(0,0, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)) 26 | self.previous_image_button = PreviousImageButton(self.images_list,self.top_layout) 27 | self.image_counter = ImageCounter(self.images_list,self.top_layout) 28 | self.next_image_button = NextImageButton(self.images_list,self.top_layout) 29 | self.top_layout.addSpacerItem(QSpacerItem(0,0, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)) 30 | self.add_videos_button = AddImagesButton(self.images_list, self.top_layout) 31 | 32 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/__init__.py -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/back_end/video_player.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import pyqtSignal, QObject 2 | from cvplayer.core.updater.video_updater import VideoUpdater 3 | from cvplayer.core.media_objects.video import Video 4 | import time 5 | 6 | class VideoPlayer(QObject): 7 | started = pyqtSignal() 8 | def __init__(self, video: Video = None, custom_class=None) -> None: 9 | super().__init__() 10 | self.video = video 11 | self.create_video_updater() 12 | self.current_time = time.time() 13 | self.custom_class = custom_class 14 | 15 | def play_video(self) -> None: 16 | self.video_updater.start_time_updater() 17 | 18 | def pause_video(self) -> None: 19 | self.video_updater.stop_time_updater() 20 | 21 | def change_frame(self, frame_id : int)-> None: 22 | if 0 <= frame_id < self.video.total_number_of_frames: 23 | self.video.set_current_frame_id(frame_id) 24 | self.video_updater.start_signal_updater() 25 | 26 | def change_frame_for_first_video(self)-> None: 27 | self.video.set_current_frame_id(0) 28 | self.video.update_to_specific_video_moment() 29 | self.show_frame() 30 | self.slider.set_range(self.video.total_number_of_frames) 31 | self.update_ui_elements() 32 | 33 | def change_speed(self, speed: int): 34 | """speed is a positive integer, 1 is normal speed, 2 is double speed, 3 is triple speed, etc.""" 35 | self.video.set_frames_to_jump(speed) 36 | 37 | def show_frame(self): 38 | if self.video.current_frame is not None: 39 | self.viewer.show_cv2_image(self.video.current_frame) 40 | 41 | def add_ui_elements(self, viewer, time_counter, slider, play): 42 | self.viewer = viewer 43 | self.time_counter = time_counter 44 | self.slider = slider 45 | self.play = play 46 | 47 | def set_video(self, video: Video): 48 | self.video_updater.stop_time_updater() 49 | self.video = video 50 | self.create_video_updater() 51 | self.change_frame(0) 52 | self.slider.set_range(self.video.total_number_of_frames) 53 | self.update_ui_elements() 54 | 55 | def update_ui_elements_each_second(self): 56 | if self.current_time + 1 < time.time(): 57 | self.current_time = time.time() 58 | self.slider.set_value(int(self.video.current_frame_id)) 59 | self.time_counter.update_time(self.video.current_milliseconds) 60 | 61 | def update_ui_elements(self): 62 | self.slider.set_value(self.video.current_frame_id) 63 | self.time_counter.update_time(self.video.current_milliseconds) 64 | self.play.set_paused() 65 | 66 | def custom_method(self): 67 | self.video.current_frame = self.custom_class.custom_method(self.video.current_frame) 68 | 69 | def create_video_updater(self): 70 | self.video_updater = VideoUpdater(self.video.fps) 71 | 72 | self.video_updater.add_method_on_timer_updater('update_frame', self.video.update_to_next_video_moment) 73 | self.video_updater.add_method_on_timer_updater('update_viewer', self.custom_method) 74 | self.video_updater.add_method_on_timer_updater('update_viewe', self.show_frame) 75 | self.video_updater.add_method_on_timer_updater('update_ui_elements', self.update_ui_elements_each_second) 76 | 77 | self.video_updater.add_method_on_signal_updater('jump_to_frame', self.video.update_to_specific_video_moment) 78 | self.video_updater.add_method_on_signal_updater('update_viewer', self.custom_method) 79 | self.video_updater.add_method_on_signal_updater('update_viewe', self.show_frame) 80 | self.video_updater.add_method_on_signal_updater('update_ui_elements', self.update_ui_elements) 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/back_end/videos_manager.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QComboBox 2 | from utils import WidgetsUtils 3 | import os 4 | 5 | class VideosManager(): 6 | def __init__(self, video_player_widget) -> None: 7 | self.video_player_widget = video_player_widget 8 | self.videos_dict = {} 9 | self.current_video = None 10 | 11 | self.populate_videos_dict(videos_path) 12 | 13 | def __init__(self, video_player, videos_path, layout, css_path='stylesheets/videos_list.css',X=30,Y=32) -> None: 14 | super().__init__() 15 | start_widget_basics(self, layout, css_path, fixed_height=Y) 16 | self.video_player = video_player 17 | self.setInsertPolicy(QComboBox.NoInsert) 18 | self.populate_video_dict(videos_path) 19 | 20 | def populate_videos_dict(self, videos_path): 21 | self.clear() 22 | self.check_video_path(videos_path) 23 | 24 | for video_path in self.videos_list: 25 | video_name = os.path.basename(video_path) 26 | self.addItem(video_name) 27 | self.videos_dict[video_name] = video_path 28 | 29 | def check_video_path(self, video_path): 30 | if not os.path.exists(video_path): 31 | raise FileNotFoundError("Video path not found") 32 | elif os.path.isdir(video_path): 33 | self.videos_list = self.find_all_mp4(video_path) 34 | elif os.path.isfile(video_path): 35 | self.videos_list = [video_path] 36 | 37 | def find_all_mp4(self, path): 38 | return glob.glob(os.path.join(path, '**', '*.mp4'), recursive=True) 39 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/__init__.py: -------------------------------------------------------------------------------- 1 | from cvplayer.core.media_player.video_player_core.interface.media_buttons import PlayPauseButton, PreviousFrameButton, NextFrameButton 2 | from cvplayer.core.media_player.video_player_core.interface.video_speed_button import VideoSpeedButton 3 | from cvplayer.core.media_player.video_player_core.interface.slider import VideoSlider 4 | from cvplayer.core.media_player.video_player_core.interface.viewer import ImageViewer 5 | from cvplayer.core.media_player.video_player_core.interface.print_screen_button import PrintScreenButton 6 | from cvplayer.core.media_player.video_player_core.interface.video_time_counter import TimeCounter 7 | from cvplayer.core.media_player.video_player_core.interface.videos_list import VideosList 8 | from cvplayer.core.media_player.video_player_core.interface.add_videos_button import AddVideosButton -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/add_videos_button.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QPushButton, QFileDialog 2 | from PyQt6.QtGui import QPixmap, QIcon 3 | from cvplayer.core.utils.widgets_utils import start_widget_basics 4 | from pkg_resources import resource_filename 5 | 6 | class AddVideosButton(QPushButton): 7 | def __init__(self, videos_list, layout=None): 8 | super().__init__() 9 | start_widget_basics(self, layout) 10 | self.set_css() 11 | self.videos_list = videos_list 12 | self.clicked.connect(self.get_videos) 13 | 14 | def get_videos(self): 15 | mp4_files = QFileDialog.getOpenFileNames(self, "Select videos", "", "Video Files (*.mp4 *.MP4 *.avi *.mov)")[0] 16 | if len(mp4_files) > 0: 17 | self.videos_list.add_videos(mp4_files) 18 | 19 | def set_css(self): 20 | add_icon_path = resource_filename(__name__, 'icons/add.png').replace('\\', '/') 21 | hover_icon_path = resource_filename(__name__, 'icons/add_hover.png').replace('\\', '/') 22 | pressed_icon_path = resource_filename(__name__, 'icons/add_pressed.png').replace('\\', '/') 23 | css_str = f""" 24 | QPushButton {{ 25 | background-color: transparent; 26 | border-radius: 10px; 27 | border-image: url({add_icon_path}); 28 | max-width: 50px; 29 | max-height: 50px; 30 | min-width: 50px; 31 | min-height: 50px; 32 | margin: 0%; 33 | padding: 0%; 34 | border: 0px; 35 | }} 36 | QPushButton:hover {{ 37 | border-image: url({hover_icon_path}); 38 | }} 39 | QPushButton:pressed {{ 40 | border-image: url({pressed_icon_path}); 41 | }} 42 | """ 43 | self.setStyleSheet(css_str) 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/ScreenShot.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/White.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/add.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/add_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/add_hover.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/add_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/add_pressed.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/black.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/cvplayerlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/cvplayerlogo.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/drop_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/drop_down.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/next.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/next2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/next2.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/pause.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/play.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/previous.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/previous2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/previous2.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/icons/solid-color-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/media_player/video_player_core/interface/icons/solid-color-image.png -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/media_buttons.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QPushButton, QToolButton 2 | import cvplayer.core.utils.widgets_utils as widgets_utils 3 | import cvplayer.core.utils.stylesheet_utils as stylesheet_utils 4 | from PyQt6.QtGui import QKeySequence 5 | from PyQt6.QtGui import QIcon 6 | from pkg_resources import resource_filename 7 | 8 | class PlayPauseButton(QToolButton): 9 | def __init__(self, video_player, layout, CSS='stylesheets/play_button.css', X=40, Y=40): 10 | super().__init__() 11 | self.start_button() 12 | widgets_utils.start_widget_basics(self, layout, CSS, fixed_width=X,fixed_height=Y) 13 | self.video_player = video_player 14 | self.pressed.connect(self.toggle) 15 | self.video_player.started.connect(self.unlock) 16 | 17 | def toggle(self): 18 | if self.is_paused: 19 | self.is_paused = False 20 | self.setIcon(QIcon(resource_filename(__name__, 'icons/pause.png'))) 21 | self.video_player.play_video() 22 | else: 23 | self.is_paused = True 24 | self.setIcon(QIcon(resource_filename(__name__, 'icons/play.png'))) 25 | self.video_player.pause_video() 26 | 27 | def set_paused(self): 28 | self.setIcon(QIcon(resource_filename(__name__, 'icons/play.png'))) 29 | self.is_paused = True 30 | 31 | def start_button(self): 32 | self.setShortcut(QKeySequence('Space')) 33 | self.setAutoRaise(True) 34 | self.setIcon(QIcon(resource_filename(__name__, 'icons/play.png'))) 35 | self.setIconSize(self.size()) 36 | self.is_paused = True 37 | self.setEnabled(False) 38 | 39 | def unlock(self): 40 | self.setEnabled(True) 41 | 42 | class NextFrameButton(QPushButton): 43 | def __init__(self, video_player, layout, X=40, Y=40): 44 | super().__init__() 45 | self.setShortcut(QKeySequence('Right')) 46 | widgets_utils.start_widget_basics(self, layout, fixed_width=X,fixed_height=Y) 47 | self.set_css() 48 | self.setEnabled(False) 49 | self.video_player = video_player 50 | self.pressed.connect(self.next_frame) 51 | self.video_player.started.connect(self.unlock) 52 | 53 | def next_frame(self): 54 | next_video_frame = self.video_player.video.current_frame_id + 1 55 | self.video_player.change_frame(next_video_frame) 56 | 57 | def unlock(self): 58 | self.setEnabled(True) 59 | 60 | def set_css(self): 61 | image_path = resource_filename(__name__, 'icons/next.png').replace("\\", "/") 62 | css_string = """ 63 | QPushButton{ 64 | border-image: url("""+image_path+"""); 65 | background:transparent; 66 | } 67 | QPushButton:hover { 68 | background-color: rgba(206, 197, 197, 0.21); 69 | } 70 | QPushButton:pressed{ 71 | background-color: rgba(132, 129, 129, 0.264); 72 | } """ 73 | self.setStyleSheet(css_string) 74 | class PreviousFrameButton(QPushButton): 75 | def __init__(self, video_player, layout, CSS = 'stylesheets/previous_frame_button.css',X=40, Y=40): 76 | super().__init__() 77 | self.setShortcut(QKeySequence('Left')) 78 | widgets_utils.start_widget_basics(self, layout, CSS, fixed_width=X,fixed_height=Y) 79 | self.set_css() 80 | self.video_player = video_player 81 | self.setEnabled(False) 82 | self.pressed.connect(self.previous_frame) 83 | self.video_player.started.connect(self.unlock) 84 | 85 | def previous_frame(self): 86 | previous_video_frame = self.video_player.video.current_frame_id - 1 87 | self.video_player.change_frame(previous_video_frame) 88 | 89 | def unlock(self): 90 | self.setEnabled(True) 91 | 92 | def set_css(self): 93 | image_path = resource_filename(__name__, 'icons/previous.png').replace("\\", "/") 94 | css_string = """ 95 | QPushButton{ 96 | border-image: url("""+image_path+"""); 97 | background:transparent; 98 | } 99 | QPushButton:hover { 100 | background-color: rgba(206, 197, 197, 0.21); 101 | } 102 | QPushButton:pressed{ 103 | background-color: rgba(132, 129, 129, 0.264); 104 | } """ 105 | self.setStyleSheet(css_string) -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/print_screen_button.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QPushButton 2 | from PIL import ImageQt 3 | import cvplayer.core.utils.widgets_utils as widgets_utils 4 | 5 | class PrintScreenButton(QPushButton): 6 | def __init__(self, widget_to_print, video_player, layout, CSS='stylesheets/print_screen_button.css', tool_tip='Captura de tela', X=30, Y=30) -> None: 7 | super().__init__() 8 | widgets_utils.start_widget_basics(self, layout, CSS, tool_tip, fixed_width=X, fixed_height=Y) 9 | self.video_player = video_player 10 | self.clicked.connect(self.print_screen) 11 | self.widget_to_print = widget_to_print 12 | 13 | def print_screen(self): 14 | QPixmap_frame = self.widget_to_print.grab() 15 | print_image = ImageQt.fromqpixmap(QPixmap_frame) 16 | print_image.save("") 17 | 18 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/slider.py: -------------------------------------------------------------------------------- 1 | # from PyQt6 import QtCore 2 | # from PyQt6 import QtWidgets 3 | # from PyQt6.QtCore import Qt 4 | 5 | # #Credits for eyllanesc, thanks bro, for real. 6 | # #His StackOverflow account -> https://stackoverflow.com/users/6622587/eyllanesc 7 | 8 | # class VideoSlider(QtWidgets.QSlider): 9 | # def __init__(self, video_player, layout ,CSS='opencvplayer/stylesheets/video_slider.css'): 10 | # super().__init__() 11 | # self.setOrientation(Qt.Horizontal) 12 | # widgets_utils.start_widget_basics(self, layout, CSS) 13 | # self.video_player = video_player 14 | # self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) 15 | # self.change_slider_limits(0, self.video_player.video.total_number_of_frames) 16 | 17 | # def mouseReleaseEvent(self, event): 18 | # super(VideoSlider, self).mouseReleaseEvent(event) 19 | # if event.button() == QtCore.Qt.LeftButton: 20 | # slider_value = self.pixel_pos_to_range_value(event.pos()) 21 | # self.setValue(slider_value) 22 | # self.video_player.change_frame(slider_value) 23 | 24 | # def set_value(self, value): 25 | # self.setValue(value) 26 | 27 | # def change_slider_limits(self, min_value, max_value): 28 | # self.setMinimum(int(min_value)) 29 | # self.setMaximum(int(max_value)) 30 | 31 | # def pixel_pos_to_range_value(self, pos): 32 | # opt = QtWidgets.QStyleOptionSlider() 33 | # self.initStyleOption(opt) 34 | # gr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderGroove, self) 35 | # sr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self) 36 | 37 | # if self.orientation() == QtCore.Qt.Horizontal: 38 | # sliderLength = sr.width() 39 | # sliderMin = gr.x() 40 | # sliderMax = gr.right() - sliderLength + 1 41 | # else: 42 | # sliderLength = sr.height() 43 | # sliderMin = gr.y() 44 | # sliderMax = gr.bottom() - sliderLength + 1; 45 | # pr = pos - sr.center() + sr.topLeft() 46 | # p = pr.x() if self.orientation() == QtCore.Qt.Horizontal else pr.y() 47 | # return QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), p - sliderMin, 48 | # sliderMax - sliderMin, opt.upsideDown) 49 | 50 | 51 | 52 | 53 | 54 | from PyQt6 import QtCore 55 | from PyQt6 import QtWidgets 56 | from PyQt6.QtWidgets import QStyle 57 | from PyQt6.QtCore import Qt 58 | import cvplayer.core.utils.widgets_utils as widgets_utils 59 | 60 | 61 | #Credits for eyllanesc, thanks bro, for real. 62 | #His StackOverflow account -> https://stackoverflow.com/users/6622587/eyllanesc 63 | 64 | class VideoSlider(QtWidgets.QSlider): 65 | def __init__(self, video_player, layout ,CSS='stylesheets/video_slider.css'): 66 | super().__init__() 67 | self.setOrientation(Qt.Orientation.Horizontal) 68 | widgets_utils.start_widget_basics(self, layout, CSS) 69 | self.video_player = video_player 70 | self.set_range(self.video_player.video.total_number_of_frames) 71 | self.setEnabled(False) 72 | self.video_player.started.connect(self.unlock) 73 | 74 | def mousePressEvent(self, e): 75 | if e.button() != Qt.MouseButton.LeftButton or not self.isEnabled(): 76 | return super().mousePressEvent(self, e) 77 | e.accept() 78 | x = e.pos().x() 79 | value = (self.maximum() - self.minimum()) * x / self.width() + self.minimum() 80 | self.setValue(int(value)) 81 | self.video_player.change_frame(value) 82 | 83 | # def mouseReleaseEvent(self, event): 84 | # super(VideoSlider, self).mouseReleaseEvent(event) 85 | # if event.button() == QtCore.Qt.MouseButton.LeftButton: 86 | # slider_value = self.pixel_pos_to_range_value(event.pos()) 87 | # self.video_player.set_video_time(slider_value) 88 | 89 | def set_range(self, duration): 90 | self.setMinimum(0) 91 | self.setMaximum(int(duration)) 92 | 93 | def set_value(self, value): 94 | self.setValue(int(value)) 95 | 96 | def unlock(self): 97 | self.setEnabled(True) -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/video_speed_button.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import Qt 2 | from PyQt6.QtWidgets import QSpinBox 3 | from PyQt6.QtGui import QShortcut 4 | import cvplayer.core.utils.widgets_utils as widgets_utils 5 | from PyQt6.QtGui import QKeySequence 6 | 7 | class VideoSpeedButton(QSpinBox): 8 | def __init__(self, video_player, layout, CSS='stylesheets/video_speed_button.css'): 9 | super().__init__() 10 | widgets_utils.start_widget_basics(self, layout, CSS, tool_tip='Change video speed by pressing up and down arrows') 11 | self.setSuffix(' x') 12 | self.valueChanged.connect(video_player.change_speed) 13 | self.setValue(1) 14 | self.setReadOnly(True) 15 | self.start_shortcuts() 16 | 17 | 18 | def increase_speed(self): 19 | self.setValue(self.value() + 1) 20 | 21 | def decrease_speed(self): 22 | if self.value() > 1: 23 | self.setValue(self.value() - 1) 24 | 25 | def start_shortcuts(self): 26 | self.increase_speed_shortcut = QShortcut(QKeySequence('Up'), self) 27 | self.increase_speed_shortcut.activated.connect(self.increase_speed) 28 | self.decrease_speed_shortcut = QShortcut(QKeySequence('Down'), self) 29 | self.decrease_speed_shortcut.activated.connect(self.decrease_speed) 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/video_time_counter.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QLabel 2 | from cvplayer.core.utils.time_and_date_utils import milliseconds_to_counter_time 3 | from cvplayer.core.utils.widgets_utils import start_widget_basics 4 | 5 | class TimeCounter(QLabel): 6 | def __init__(self, video_player, layout, CSS='stylesheets/video_time_counter.css'): 7 | super().__init__() 8 | self.video_player = video_player 9 | start_widget_basics(self, layout, CSS) 10 | self.setText("00:00:00") 11 | self.setScaledContents(True) 12 | #self.setStyleSheet("background-color: rgb(200,200,200); font-size: 20px; color: black;") 13 | 14 | #layout.addWidget(self) 15 | 16 | def update_time(self, time_in_miliseconds): 17 | self.setText(milliseconds_to_counter_time(time_in_miliseconds)) -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/videos_list.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QComboBox 2 | from cvplayer.core.utils.widgets_utils import start_widget_basics 3 | from cvplayer.core.media_objects.video import Video 4 | import os 5 | from pkg_resources import resource_filename 6 | 7 | class VideosList(QComboBox): 8 | videos_dict = {} 9 | current_video_path = None 10 | video_player = None 11 | started = False 12 | def __init__(self, video_player, layout,X=350,Y=40) -> None: 13 | super().__init__() 14 | start_widget_basics(self, layout, fixed_height=Y) 15 | self.set_css() 16 | self.video_player = video_player 17 | self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) 18 | self.setMinimumWidth(X) 19 | self.currentIndexChanged.connect(self.change_video) 20 | 21 | def change_video(self, video_index): 22 | self.current_video_path = self.videos_dict[self.currentText()] 23 | self.video_player.set_video(Video(self.current_video_path)) 24 | self.video_player.started.emit() 25 | 26 | def add_videos(self, videos_path): 27 | for video_path in videos_path: 28 | self.check_video_path(video_path) 29 | video_name = os.path.basename(video_path) 30 | self.videos_dict[video_name] = video_path 31 | self.addItem(video_name) 32 | 33 | def check_video_path(self, video_path): 34 | if not os.path.exists(video_path): 35 | raise FileNotFoundError("Video path not found") 36 | elif os.path.isfile(video_path): 37 | self.videos_list = [video_path] 38 | 39 | def set_css(self): 40 | image_path = resource_filename(__name__, 'icons/drop_down.png').replace("\\", "/") 41 | css_str = """ 42 | QComboBox { 43 | border-radius: 7px; 44 | color: lightgray; 45 | background-color: #333333; 46 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 47 | min-width: 350px; 48 | max-width: 350px; 49 | } 50 | QComboBox::hover{ 51 | 52 | background-color: #4e5050; 53 | border-radius: 5px; 54 | } 55 | QComboBox:!editable:on, QComboBox::drop-down:editable:on { 56 | background: #4e5050; 57 | } 58 | QComboBox::drop-down { 59 | subcontrol-origin: padding; 60 | subcontrol-position: top right; 61 | width: 30px; 62 | border-bottom-right-radius: 3px; 63 | border-radius: 5px; 64 | } 65 | QComboBox::down-arrow { 66 | image: url(""" + image_path + """); 67 | height: 15px; 68 | width: 15px; 69 | } 70 | QComboBox QAbstractItemView { 71 | border-radius: 5px; 72 | selection-color: #4e5050; 73 | selection-background-color: #4e5050; 74 | color: rgba(91,91,91,255); 75 | background-color: #4e5050; 76 | outline: 0px; 77 | font-size: 12pt; 78 | font-weight: 400; 79 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 80 | } 81 | QListView::item { 82 | height:40px; 83 | outline: 0px; 84 | } 85 | QListView::item:selected { 86 | background-color: #4e5050; 87 | outline: 0px; 88 | } 89 | QScrollBar{ 90 | width:0px; 91 | }""" 92 | self.setStyleSheet(css_str) -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_core/interface/viewer.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QLabel 2 | import cvplayer.core.utils.widgets_utils as widgets_utils 3 | import cvplayer.core.utils.video_utils as video_utils 4 | 5 | 6 | class ImageViewer(QLabel): 7 | def __init__(self, layout, CSS='stylesheets/image_viewer.css'): 8 | super().__init__() 9 | self.setScaledContents(True) 10 | widgets_utils.start_widget_basics(self, layout, CSS) 11 | self.cv2_image = None 12 | 13 | def show_cv2_image(self, cv2_image): 14 | QPixmap = video_utils.cv2_image_to_QPixmap(cv2_image) 15 | self.setPixmap(QPixmap) 16 | 17 | # from PyQt5 import QtCore, QtGui, QtWidgets 18 | # import time 19 | 20 | # class ImageViewer(QtWidgets.QGraphicsView): 21 | # photoClicked = QtCore.pyqtSignal(QtCore.QPoint) 22 | # change_image_on_viewer = QtCore.pyqtSignal() 23 | 24 | # def __init__(self, layout, CSS=None): 25 | # super(ImageViewer, self).__init__() 26 | # widgets_utils.start_widget_basics(self, layout, CSS) 27 | # self.cv2_image = None 28 | 29 | # self._zoom = 0 30 | # self._empty = True 31 | # self._scene = QtWidgets.QGraphicsScene(self) 32 | # self._photo = QtWidgets.QGraphicsPixmapItem() 33 | # self._scene.addItem(self._photo) 34 | # self.setScene(self._scene) 35 | # self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse) 36 | # self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse) 37 | # self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 38 | # self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 39 | # self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30))) 40 | # self.setFrameShape(QtWidgets.QFrame.NoFrame) 41 | # self.change_image_on_viewer.connect(self.setPhoto) 42 | 43 | # def show_cv2_image(self, cv2_image): 44 | # self.QPixmap = video_utils.cv2_image_to_QPixmap(cv2_image) 45 | # self.change_image_on_viewer.emit() 46 | 47 | # def hasPhoto(self): 48 | # return not self._empty 49 | 50 | # def fitInView(self, scale=True): 51 | # rect = QtCore.QRectF(self._photo.pixmap().rect()) 52 | # self.setSceneRect(rect) 53 | # unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1)) 54 | # self.scale(1 / unity.width(), 1 / unity.height()) 55 | # viewrect = self.viewport().rect() 56 | # scenerect = self.transform().mapRect(rect) 57 | # factor = min(viewrect.width() / scenerect.width(), 58 | # viewrect.height() / scenerect.height()) 59 | # self.scale(factor, factor) 60 | # self._zoom = 0 61 | 62 | # def setPhoto(self): 63 | # start = time.time() 64 | # self._zoom = 0 65 | # self._empty = False 66 | # self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag) 67 | # self._photo.setPixmap(self.QPixmap) 68 | 69 | # self.fitInView() 70 | # end = time.time() 71 | # print("Time to set photo: ", end - start) 72 | 73 | # def wheelEvent(self, event): 74 | # if self.hasPhoto(): 75 | # if event.angleDelta().y() > 0: 76 | # factor = 1.25 77 | # self._zoom += 1 78 | # else: 79 | # factor = 0.8 80 | # self._zoom -= 1 81 | # if self._zoom > 0: 82 | # self.scale(factor, factor) 83 | # elif self._zoom == 0: 84 | # self.fitInView() 85 | # else: 86 | # self._zoom = 0 87 | 88 | # def toggleDragMode(self): 89 | # if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag: 90 | # self.setDragMode(QtWidgets.QGraphicsView.NoDrag) 91 | # elif not self._photo.pixmap().isNull(): 92 | # self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag) 93 | 94 | # def mousePressEvent(self, event): 95 | # if self._photo.isUnderMouse(): 96 | # self.photoClicked.emit(self.mapToScene(event.pos()).toPoint()) 97 | # super(ImageViewer, self).mousePressEvent(event) 98 | 99 | 100 | -------------------------------------------------------------------------------- /cvplayer/core/media_player/video_player_widget.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy 2 | from cvplayer.core.media_player.video_player_core.interface import * 3 | from cvplayer.core.media_objects.video import Video 4 | from cvplayer.core.media_player.video_player_core.back_end.video_player import VideoPlayer 5 | from cvplayer.core.utils.widgets_utils import start_widget_basics 6 | import os 7 | from pkg_resources import resource_filename 8 | 9 | class VideoPlayerWidget(QWidget): 10 | def __init__(self, custom_class) -> None: 11 | super().__init__() 12 | start_widget_basics(self, None, 'stylesheets/video_player_widget.css', minimum_height=300, minimum_width=300) 13 | self.video = Video(resource_filename(__name__, 'default.mp4')) 14 | self.custom_class = custom_class 15 | self.video_player = VideoPlayer(self.video, self.custom_class) 16 | self.build_ui_elements() 17 | self.video_player.change_frame_for_first_video() 18 | 19 | def build_ui_elements(self): 20 | self.main_layout = QVBoxLayout(self) 21 | self.top_layout = QHBoxLayout() 22 | self.bottom_layout = QHBoxLayout() 23 | 24 | self.main_layout.addLayout(self.top_layout) 25 | self.videos_list = VideosList(self.video_player, self.top_layout) 26 | spacer = QSpacerItem(1, 10, hPolicy= QSizePolicy.Policy.Expanding) 27 | self.top_layout.addItem(spacer) 28 | self.add_videos_button = AddVideosButton(self.videos_list, self.top_layout) 29 | 30 | self.image_viewer = ImageViewer(self.main_layout) 31 | self.main_layout.addLayout(self.bottom_layout) 32 | 33 | self.previous_frame_button = PreviousFrameButton(self.video_player,self.bottom_layout) 34 | self.play_pause_button = PlayPauseButton(self.video_player,self.bottom_layout) 35 | self.next_frame_button = NextFrameButton(self.video_player,self.bottom_layout) 36 | self.video_slider = VideoSlider(self.video_player,self.bottom_layout) 37 | self.time_counter = TimeCounter(self.video_player,self.bottom_layout) 38 | self.video_speed_button = VideoSpeedButton(self.video_player,self.bottom_layout) 39 | 40 | self.video_player.add_ui_elements(self.image_viewer, self.time_counter, self.video_slider, self.play_pause_button) 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /cvplayer/core/references.txt: -------------------------------------------------------------------------------- 1 | Icons used: 2 | 3 | Reproduzir icon by Icons8 4 | Retroceder icon by Icons8 5 | Avanço rápido icon by Icons8 6 | Seta para expandir icon by Icons8 7 | Mais 2 matemática icon by Icons8 8 | Mais 2 matemática icon by Icons8 9 | Avançar icon by Icons8 10 | Avançar icon by Icons8 11 | -------------------------------------------------------------------------------- /cvplayer/core/updater/hybrid_updater.py: -------------------------------------------------------------------------------- 1 | from .time_updater import TimeUpdater 2 | import time 3 | from typing import NewType 4 | 5 | method = NewType('method', int) 6 | positive_int = NewType('positive_int', int) 7 | 8 | class HybridUpdater(TimeUpdater): 9 | __time_updater_boolean = False 10 | __signal_updater_boolean = False 11 | __time_updater_methods_dict = {} 12 | __signal_updater_methods_dict = {} 13 | 14 | def __init__(self, time_interval: positive_int = 1) -> None: 15 | super().__init__(time_interval) 16 | 17 | def start_time_updater(self) -> None: 18 | self.__time_updater_boolean = True 19 | self.__signal_updater_boolean = False 20 | self.quit() 21 | self.start() 22 | 23 | def stop_time_updater(self) -> None: 24 | self.__time_updater_boolean = False 25 | self.quit() 26 | 27 | def start_signal_updater(self) -> None: 28 | self.quit() 29 | self.__time_updater_boolean = False 30 | self.__signal_updater_boolean = True 31 | self.start() 32 | 33 | def __run_time_updater(self) -> None: 34 | while self.__time_updater_boolean: 35 | for method in self.__time_updater_methods_dict.values(): 36 | method() 37 | time.sleep(self.time_interval) 38 | 39 | def __run_signal_updater(self) -> None: 40 | [method() for method in self.__signal_updater_methods_dict.values()] 41 | self.quit() 42 | 43 | def run(self) -> None: 44 | if self.__time_updater_boolean: 45 | self.__run_time_updater() 46 | elif self.__signal_updater_boolean: 47 | self.__run_signal_updater() 48 | 49 | def add_method_on_timer_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 50 | self.__time_updater_methods_dict[methodKey] = method_whitout_parameters 51 | 52 | def add_method_on_signal_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 53 | self.__signal_updater_methods_dict[methodKey] = method_whitout_parameters 54 | 55 | def remove_method_from_time_updater(self, method_key: str) -> str: 56 | return self.__time_updater_methods_dict.pop(method_key) 57 | 58 | def remove_method_from_signal_updater(self, method_key: str) -> str: 59 | return self.__signal_updater_methods_dict.pop(method_key) 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /cvplayer/core/updater/old_video_updater.py: -------------------------------------------------------------------------------- 1 | from .time_updater import TimeUpdater 2 | import time 3 | from typing import NewType 4 | 5 | method = NewType('method', int) 6 | positive_int = NewType('positive_int', int) 7 | 8 | class VideoUpdater(TimeUpdater): 9 | __time_updater_boolean = False 10 | __signal_updater_boolean = False 11 | 12 | def __init__(self, video_fps: float = 29.97, time_interval: positive_int = 0) -> None: 13 | super().__init__(time_interval) 14 | self.time_beetwen_frames = 1 / video_fps 15 | self.__time_updater_methods_dict = {} 16 | self.__signal_updater_methods_dict = {} 17 | 18 | def start_time_updater(self) -> None: 19 | self.quit() 20 | self.__time_updater_boolean = True 21 | self.__signal_updater_boolean = False 22 | self.start() 23 | 24 | def stop_time_updater(self) -> None: 25 | now = time.time() 26 | self.quit() 27 | 28 | self.__time_updater_boolean = False 29 | ####Extremamente perigoso!, concertar isso 30 | 31 | while self.isRunning() == True:## and now + 0.2 > time.time(): 32 | continue 33 | 34 | def start_signal_updater(self) -> None: 35 | self.stop_time_updater() 36 | self.__time_updater_boolean = False 37 | self.__signal_updater_boolean = True 38 | self.start() 39 | 40 | def __run_time_updater(self) -> None: 41 | while self.__time_updater_boolean: 42 | start = time.time() 43 | for method in self.__time_updater_methods_dict.values(): 44 | method() 45 | time.sleep(max(0, self.time_beetwen_frames - (time.time() - start))) 46 | 47 | def __run_signal_updater(self) -> None: 48 | [method() for method in self.__signal_updater_methods_dict.values()] 49 | self.quit() 50 | 51 | def run(self) -> None: 52 | if self.__time_updater_boolean: 53 | self.__run_time_updater() 54 | elif self.__signal_updater_boolean: 55 | self.__run_signal_updater() 56 | 57 | def add_method_on_timer_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 58 | self.__time_updater_methods_dict[methodKey] = method_whitout_parameters 59 | 60 | def add_method_on_signal_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 61 | self.__signal_updater_methods_dict[methodKey] = method_whitout_parameters 62 | 63 | def remove_method_from_time_updater(self, method_key: str) -> str: 64 | return self.__time_updater_methods_dict.pop(method_key) 65 | 66 | def remove_method_from_signal_updater(self, method_key: str) -> str: 67 | return self.__signal_updater_methods_dict.pop(method_key) 68 | 69 | def is_time_updater_running(self) -> bool: 70 | return self.__time_updater_boolean 71 | 72 | 73 | 74 | # qthread.quit(); // Cause the thread to cease. 75 | # qthread.wait(); 76 | 77 | 78 | # myThread->m_abort = true; //Tell the thread to abort 79 | # if(!myThread->wait(5000)) //Wait until it actually has terminated (max. 5 sec) 80 | # { 81 | # myThread->terminate(); //Thread didn't exit in time, probably deadlocked, terminate it! 82 | # myThread->wait(); //We have to wait again here! 83 | # } -------------------------------------------------------------------------------- /cvplayer/core/updater/time_updater.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import NewType 3 | from .updater import Updater 4 | 5 | positive_int = NewType('positive_int', int) 6 | 7 | class TimeUpdater(Updater): 8 | run_updater = False 9 | 10 | def __init__(self, time_interval: positive_int = 1) -> None: 11 | super().__init__() 12 | """Executes the methods in a loop with a time interval in seconds.""" 13 | self.time_interval = time_interval 14 | 15 | def run(self)-> None: 16 | while self.run_updater: 17 | for method in self.__methods_dict.values(): 18 | method() 19 | time.sleep(self.time_interval) 20 | 21 | def start_updater(self)-> None: 22 | self.run_updater = True 23 | self.start() 24 | 25 | def stop_updater(self)-> None: 26 | self.run_updater = False 27 | self.quit() 28 | 29 | def set_time_interval(self, time_interval: positive_int)-> None: 30 | self.time_interval = time_interval 31 | 32 | -------------------------------------------------------------------------------- /cvplayer/core/updater/updater.py: -------------------------------------------------------------------------------- 1 | from typing import NewType 2 | from PyQt6.QtCore import QThread 3 | 4 | method = NewType('method', int) 5 | 6 | class Updater(QThread): 7 | __methods_dict = {} 8 | 9 | def __init__(self) -> None: 10 | super().__init__() 11 | """Executes the methods added to it when runed.""" 12 | 13 | def run(self)-> None: 14 | [method() for method in self.__methods_dict.values()] 15 | self.quit() 16 | 17 | def add_method_on_timer_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 18 | self.__methods_dict[methodKey] = method_whitout_parameters 19 | 20 | def remove_method(self, method_key: str) -> str: 21 | return self.__methods_dict.pop(method_key) 22 | 23 | def start_updater(self)-> None: 24 | self.start() 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /cvplayer/core/updater/video_updater.py: -------------------------------------------------------------------------------- 1 | from .time_updater import TimeUpdater 2 | import time 3 | import queue 4 | from typing import NewType 5 | 6 | method = NewType('method', int) 7 | positive_int = NewType('positive_int', int) 8 | 9 | class VideoUpdater(TimeUpdater): 10 | __time_updater_boolean = False 11 | __signal_updater_boolean = False 12 | __running = False 13 | 14 | def __init__(self, video_fps: float = 29.97, time_interval: positive_int = 0) -> None: 15 | super().__init__(time_interval) 16 | self.tasks_queue = queue.Queue() 17 | self.time_beetwen_frames = 1 / video_fps 18 | self.__time_updater_methods_dict = {} 19 | self.__signal_updater_methods_dict = {} 20 | 21 | def add_task(self, task) -> None: 22 | if self.__running == False: 23 | self.tasks_queue.put(task) 24 | self.start() 25 | elif self.__running == True: 26 | self.tasks_queue.put(task) 27 | 28 | def start_signal_updater(self) -> None: 29 | self.__time_updater_boolean = False 30 | self.__signal_updater_boolean = True 31 | self.add_task( self.__signal_updater_methods_dict) 32 | 33 | def start_time_updater(self) -> None: 34 | self.__time_updater_boolean = True 35 | self.__signal_updater_boolean = False 36 | self.add_task( self.__time_updater_methods_dict) 37 | 38 | def stop_time_updater(self) -> None: 39 | self.__time_updater_boolean = False 40 | 41 | def run(self) -> None: 42 | self.__running = True 43 | while self.tasks_queue.empty() == False: 44 | task = self.tasks_queue.get() 45 | for task_method in task.values(): 46 | task_method() 47 | if self.__time_updater_boolean: 48 | start = time.time() 49 | self.tasks_queue.put( self.__time_updater_methods_dict) 50 | time.sleep(max(0, self.time_beetwen_frames - (time.time() - start))) 51 | self.__running = False 52 | 53 | def add_method_on_timer_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 54 | self.__time_updater_methods_dict[methodKey] = method_whitout_parameters 55 | 56 | def add_method_on_signal_updater(self, methodKey: str, method_whitout_parameters: method) -> None: 57 | self.__signal_updater_methods_dict[methodKey] = method_whitout_parameters 58 | 59 | def remove_method_from_time_updater(self, method_key: str) -> str: 60 | return self.__time_updater_methods_dict.pop(method_key) 61 | 62 | def remove_method_from_signal_updater(self, method_key: str) -> str: 63 | return self.__signal_updater_methods_dict.pop(method_key) 64 | 65 | def is_time_updater_running(self) -> bool: 66 | return self.__time_updater_boolean -------------------------------------------------------------------------------- /cvplayer/core/utils/json_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def save_json(dict, path): 4 | with open(path, 'w') as json_path: 5 | json.dump(dict, json_path) 6 | 7 | def load_json(json_path): 8 | with open(json_path, 'r') as json_file: 9 | json_data = json.load(json_file) 10 | return json_data 11 | 12 | def labelme_to_egetra(labelme_dict): 13 | return {'latitude': labelme_dict['latitude'], 'longitude': labelme_dict['longitude'], 'data': labelme_dict['tempo'], 'shapes': labelme_dict['shapes']} 14 | -------------------------------------------------------------------------------- /cvplayer/core/utils/layout_utils.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QVBoxLayout 2 | from PyQt6 import QtCore 3 | 4 | def add_widget_in_layout(widget,layout, alignment= QtCore.Qt.AlignmentFlag.AlignVCenter): 5 | layout.addWidget(widget)#, alignment= alignment) 6 | 7 | def add_layout_in_layout(layout_father, layout_son): 8 | layout_father.addLayout(layout_son) 9 | 10 | def add_spacer_in_layout(layout, spacer): 11 | layout.addSpacerItem(spacer) 12 | QVBoxLayout.addWidget -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheet_utils.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import resource_filename 2 | 3 | def load_css(css_path): 4 | readed_file = open(resource_filename(__name__, css_path),"r") 5 | return readed_file.read() 6 | 7 | def set_style_sheet(widget, css_path): 8 | qcss = load_css(css_path) 9 | widget.setStyleSheet (qcss) 10 | 11 | -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/add_videos_button.css: -------------------------------------------------------------------------------- 1 | QPushButton { 2 | background-color: transparent; 3 | border-radius: 10px; 4 | border-image: url('cvplayer/icons/add.png'); 5 | max-width: 50px; 6 | max-height: 50px; 7 | min-width: 50px; 8 | min-height: 50px; 9 | margin: 0%; 10 | padding: 0%; 11 | border: 0px; 12 | } 13 | QPushButton:hover { 14 | border-image: url('cvplayer/icons/add_hover.png'); 15 | } 16 | QPushButton:pressed { 17 | border-image: url('cvplayer/icons/add_pressed.png'); 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/image_counter.css: -------------------------------------------------------------------------------- 1 | QLabel{ 2 | background-color:rgba(31,29,30,255); 3 | color: white; 4 | font-size: 20px; 5 | font-weight: bold; 6 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/image_player_widget.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/utils/stylesheets/image_player_widget.css -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/image_viewer.css: -------------------------------------------------------------------------------- 1 | QLabel { 2 | background-color: rgba(23,28,34,255); 3 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/next_frame_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/next.png'); 3 | background:transparent; 4 | } 5 | QPushButton:hover { 6 | background-color: rgba(206, 197, 197, 0.21); 7 | } 8 | QPushButton:pressed{ 9 | background-color: rgba(132, 129, 129, 0.264); 10 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/next_image_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/next2.png'); 3 | background:transparent; 4 | border-radius: 10px; 5 | border: none; 6 | outline: none; 7 | } 8 | QPushButton:hover { 9 | background-color: rgba(206, 197, 197, 0.21); 10 | } 11 | QPushButton:pressed{ 12 | background-color: rgba(132, 129, 129, 0.264); 13 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/pause_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/pause.png'); 3 | background:transparent; 4 | } 5 | QPushButton:hover { 6 | background-color: rgba(206, 197, 197, 0.103); 7 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/play_button.css: -------------------------------------------------------------------------------- 1 | QToolButton{ 2 | background:transparent; 3 | border: none 4 | } 5 | QToolButton:hover { 6 | background-color: rgba(206, 197, 197, 0.21); 7 | } 8 | QToolButton:pressed{ 9 | background-color: rgba(132, 129, 129, 0.264); 10 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/previous_frame_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/previous.png'); 3 | background:transparent; 4 | } 5 | QPushButton:hover { 6 | background-color: rgba(206, 197, 197, 0.21); 7 | } 8 | QPushButton:pressed{ 9 | background-color: rgba(132, 129, 129, 0.264); 10 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/previous_image_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/previous2.png'); 3 | background:transparent; 4 | border-radius: 10px; 5 | border: none; 6 | outline: none; 7 | } 8 | QPushButton:hover { 9 | background-color: rgba(206, 197, 197, 0.21); 10 | } 11 | QPushButton:pressed{ 12 | background-color: rgba(132, 129, 129, 0.264); 13 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/print_screen_button.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/utils/stylesheets/print_screen_button.css -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/video_player_widget.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/core/utils/stylesheets/video_player_widget.css -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/video_slider.css: -------------------------------------------------------------------------------- 1 | QSlider::sub-page:Horizontal { 2 | background-color: #99eef1; } 3 | 4 | 5 | QSlider::add-page:Horizontal { 6 | background-color: #333333; } 7 | 8 | QSlider::groove:Horizontal { 9 | background: transparent; 10 | height:4px; } 11 | 12 | QSlider::handle:Horizontal { 13 | width:10px; 14 | border-radius:5px; 15 | background:#898989; 16 | margin: -5px 0px -5px 0px; } 17 | 18 | 19 | QSlider { 20 | background-color: rgba(0,0,0,0%); } 21 | -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/video_speed_button.css: -------------------------------------------------------------------------------- 1 | QSpinBox { 2 | border-radius: 10px; 3 | background-color: rgb(31, 29, 30); 4 | color: white; 5 | font-size: 20px; 6 | font-weight: bold; 7 | padding: 5px 0px 5px 0px; 8 | 9 | 10 | } 11 | 12 | /* background color when line edit is selected */ 13 | 14 | QSpinBox::up-button { 15 | width: 0px; 16 | } 17 | 18 | QSpinBox::down-button { 19 | width: 0px; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/video_time_counter.css: -------------------------------------------------------------------------------- 1 | QLabel{ 2 | background-color:rgba(31,29,30,255); 3 | color: white; 4 | font-size: 20px; 5 | } -------------------------------------------------------------------------------- /cvplayer/core/utils/stylesheets/videos_list.css: -------------------------------------------------------------------------------- 1 | QComboBox { 2 | border-radius: 7px; 3 | color: lightgray; 4 | background-color: #333333; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | min-width: 350px; 7 | max-width: 350px; 8 | } 9 | QComboBox::hover{ 10 | 11 | background-color: #4e5050; 12 | border-radius: 5px; 13 | } 14 | QComboBox:!editable:on, QComboBox::drop-down:editable:on { 15 | background: #4e5050; 16 | } 17 | QComboBox::drop-down { 18 | subcontrol-origin: padding; 19 | subcontrol-position: top right; 20 | width: 30px; 21 | border-bottom-right-radius: 3px; 22 | border-radius: 5px; 23 | } 24 | QComboBox::down-arrow { 25 | image: url('cvplayer/icons/drop_down.png'); 26 | height: 15px; 27 | width: 15px; 28 | } 29 | QComboBox QAbstractItemView { 30 | border-radius: 5px; 31 | selection-color: #4e5050; 32 | selection-background-color: #4e5050; 33 | color: rgba(91,91,91,255); 34 | background-color: #4e5050; 35 | outline: 0px; 36 | font-size: 12pt; 37 | font-weight: 400; 38 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 39 | } 40 | QListView::item { 41 | height:40px; 42 | outline: 0px; 43 | } 44 | QListView::item:selected { 45 | background-color: #4e5050; 46 | outline: 0px; 47 | } 48 | QScrollBar{ 49 | width:0px; 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /cvplayer/core/utils/time_and_date_utils.py: -------------------------------------------------------------------------------- 1 | def milliseconds_to_counter_time(miliseconds_time): 2 | seconds=int((miliseconds_time/1000)%60) 3 | minutes=int((miliseconds_time/(1000*60))%60) 4 | hours=int((miliseconds_time/(1000*60*60))%24) 5 | 6 | return "%02d:%02d:%02d" % (hours, minutes, seconds) -------------------------------------------------------------------------------- /cvplayer/core/utils/video_utils.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtGui import QPixmap, QImage 2 | import cv2 3 | 4 | def calculate_time_beetwen_frames(video_fps): 5 | return 1 / video_fps 6 | 7 | #temporariamente aqui 8 | def cv2_image_to_QPixmap(cv2_image): 9 | height, width, channel = cv2_image.shape 10 | bytes_per_line = 3 * width 11 | qt_image = QImage(cv2_image.data, width, height, bytes_per_line, QImage.Format.Format_BGR888) 12 | 13 | return QPixmap(qt_image) 14 | 15 | def get_video_thumbnail(road_video_path: str): 16 | video = cv2.VideoCapture(road_video_path) 17 | success, cv2_thumbnail = video.read() 18 | if success: 19 | return cv2_image_to_QPixmap(cv2_thumbnail) 20 | else: 21 | print("Error: Could not get thumbnail from video") 22 | video.release() 23 | cv2.destroyAllWindows() -------------------------------------------------------------------------------- /cvplayer/core/utils/widgets_utils.py: -------------------------------------------------------------------------------- 1 | from cvplayer.core.utils import layout_utils, stylesheet_utils 2 | from PyQt6.QtWidgets import QWidget, QLayout 3 | from PyQt6.QtCore import Qt 4 | 5 | 6 | def start_widget_basics(widget: QWidget, layout: QLayout = None, css_path: str = None, tool_tip: str = None, minimum_width: int=None, minimum_height: int=None, fixed_width: int=None, fixed_height: int=None, alignment: Qt.AlignmentFlag = None): 7 | """Initialize widget CSS, Layout, ToolTip and Minimum Sizes""" 8 | if css_path!=None: 9 | stylesheet_utils.set_style_sheet(widget, css_path) 10 | if layout!=None: 11 | layout_utils.add_widget_in_layout(widget, layout) 12 | if tool_tip!=None: 13 | widget.setToolTip(tool_tip) 14 | if minimum_width!=None: 15 | widget.setMinimumWidth(minimum_width) 16 | if minimum_height!=None: 17 | widget.setMinimumHeight(minimum_height) 18 | if fixed_width!=None: 19 | widget.setFixedWidth(fixed_width) 20 | if fixed_height!=None: 21 | widget.setFixedHeight(fixed_height) 22 | if alignment!=None and layout!=None: 23 | layout.setAlignment(alignment) 24 | -------------------------------------------------------------------------------- /cvplayer/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y python3 5 | RUN apt-get install -y python3-pip 6 | RUN apt-get install libxcb-xinerama0 7 | 8 | RUN apt-get install -y libsm6 libxext6 libxrender-dev 9 | 10 | RUN apt-get update 11 | RUN pip install numpy==1.24.2 12 | 13 | RUN apt-get update 14 | RUN pip install setuptools==65.6.3 wheel==0.38.4 15 | 16 | RUN pip3 install PyQt6==6.4.2 PyQt6-Qt6==6.4.2 PyQt6-sip==13.4.1 17 | 18 | RUN pip3 install opencv-python-headless 19 | 20 | RUN pip install pillow 21 | 22 | RUN apt-get update 23 | RUN apt install qt6-base-abi -y 24 | 25 | RUN apt-get update 26 | RUN apt install qt6-base-dev -y 27 | 28 | RUN apt-get update 29 | RUN apt-get install -y libgl1-mesa-glx 30 | 31 | RUN apt-get update 32 | RUN apt-get install -y git 33 | 34 | WORKDIR /home 35 | RUN git clone https://github.com/edu010101/opencvplayer 36 | 37 | WORKDIR /home/opencvplayer/opencvplayer 38 | 39 | RUN pip3 install -e . 40 | 41 | WORKDIR /home/opencvplayer 42 | 43 | ENV DEBIAN_FRONTEND=noninteractive 44 | 45 | RUN adduser --quiet --disabled-password qtuser && usermod -a -G audio qtuser 46 | 47 | ENV LIBGL_ALWAYS_INDIRECT=1 48 | 49 | COPY ./opencvplayer/test.py /home/opencvplayer/test.py 50 | COPY ./opencvplayer/cut.mp4 /home/opencvplayer/cut.avi 51 | 52 | ENTRYPOINT python3 test.py 53 | 54 | #ENTRYPOINT bash 55 | -------------------------------------------------------------------------------- /cvplayer/docker/runing_docker.txt: -------------------------------------------------------------------------------- 1 | HOW TO RUN THE DOCKER 2 | 3 | paste the following command making changes on the nescessary places 4 | 5 | sudo docker run --rm -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY -u qtuser containername 6 | 7 | -------------------------------------------------------------------------------- /cvplayer/icons/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/ScreenShot.png -------------------------------------------------------------------------------- /cvplayer/icons/White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/White.png -------------------------------------------------------------------------------- /cvplayer/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/add.png -------------------------------------------------------------------------------- /cvplayer/icons/add_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/add_hover.png -------------------------------------------------------------------------------- /cvplayer/icons/add_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/add_pressed.png -------------------------------------------------------------------------------- /cvplayer/icons/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/black.png -------------------------------------------------------------------------------- /cvplayer/icons/cvplayerlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/cvplayerlogo.png -------------------------------------------------------------------------------- /cvplayer/icons/drop_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/drop_down.png -------------------------------------------------------------------------------- /cvplayer/icons/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example.png -------------------------------------------------------------------------------- /cvplayer/icons/example2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example2.mp4 -------------------------------------------------------------------------------- /cvplayer/icons/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example2.png -------------------------------------------------------------------------------- /cvplayer/icons/example3.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example3.mp4 -------------------------------------------------------------------------------- /cvplayer/icons/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example3.png -------------------------------------------------------------------------------- /cvplayer/icons/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example4.png -------------------------------------------------------------------------------- /cvplayer/icons/example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/example5.png -------------------------------------------------------------------------------- /cvplayer/icons/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/next.png -------------------------------------------------------------------------------- /cvplayer/icons/next2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/next2.png -------------------------------------------------------------------------------- /cvplayer/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/pause.png -------------------------------------------------------------------------------- /cvplayer/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/play.png -------------------------------------------------------------------------------- /cvplayer/icons/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/previous.png -------------------------------------------------------------------------------- /cvplayer/icons/previous2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/previous2.png -------------------------------------------------------------------------------- /cvplayer/icons/solid-color-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/icons/solid-color-image.png -------------------------------------------------------------------------------- /cvplayer/image_player.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QApplication, QMainWindow 2 | from cvplayer.core.media_player.image_player_widget import ImagePlayerWidget 3 | import sys 4 | 5 | class ImagePlayer(): 6 | def __init__(self, custom_class) -> None: 7 | app = QApplication(sys.argv) 8 | if not hasattr(custom_class, 'custom_method') or not callable(getattr(custom_class, 'custom_method')): 9 | raise RuntimeError('custom_method not found in the class') 10 | image_player = ImagePlayerWidget(custom_class) 11 | window = QMainWindow() 12 | window.setStyleSheet('background-color: rgba(31,29,30,255);') 13 | window.setMinimumSize(550,400) 14 | window.setCentralWidget(image_player) 15 | window.show() 16 | sys.exit(app.exec()) 17 | 18 | -------------------------------------------------------------------------------- /cvplayer/stylesheets/add_videos_button.css: -------------------------------------------------------------------------------- 1 | QPushButton { 2 | background-color: transparent; 3 | border-radius: 10px; 4 | border-image: url('cvplayer/icons/add.png'); 5 | max-width: 50px; 6 | max-height: 50px; 7 | min-width: 50px; 8 | min-height: 50px; 9 | margin: 0%; 10 | padding: 0%; 11 | border: 0px; 12 | } 13 | QPushButton:hover { 14 | border-image: url('cvplayer/icons/add_hover.png'); 15 | } 16 | QPushButton:pressed { 17 | border-image: url('cvplayer/icons/add_pressed.png'); 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /cvplayer/stylesheets/image_counter.css: -------------------------------------------------------------------------------- 1 | QLabel{ 2 | background-color:rgba(31,29,30,255); 3 | color: white; 4 | font-size: 20px; 5 | font-weight: bold; 6 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/image_player_widget.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/stylesheets/image_player_widget.css -------------------------------------------------------------------------------- /cvplayer/stylesheets/image_viewer.css: -------------------------------------------------------------------------------- 1 | QLabel { 2 | background-color: rgba(23,28,34,255); 3 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/next_frame_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/next.png'); 3 | background:transparent; 4 | } 5 | QPushButton:hover { 6 | background-color: rgba(206, 197, 197, 0.21); 7 | } 8 | QPushButton:pressed{ 9 | background-color: rgba(132, 129, 129, 0.264); 10 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/next_image_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/next2.png'); 3 | background:transparent; 4 | border-radius: 10px; 5 | border: none; 6 | outline: none; 7 | } 8 | QPushButton:hover { 9 | background-color: rgba(206, 197, 197, 0.21); 10 | } 11 | QPushButton:pressed{ 12 | background-color: rgba(132, 129, 129, 0.264); 13 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/pause_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/pause.png'); 3 | background:transparent; 4 | } 5 | QPushButton:hover { 6 | background-color: rgba(206, 197, 197, 0.103); 7 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/play_button.css: -------------------------------------------------------------------------------- 1 | QToolButton{ 2 | background:transparent; 3 | border: none 4 | } 5 | QToolButton:hover { 6 | background-color: rgba(206, 197, 197, 0.21); 7 | } 8 | QToolButton:pressed{ 9 | background-color: rgba(132, 129, 129, 0.264); 10 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/previous_frame_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/previous.png'); 3 | background:transparent; 4 | } 5 | QPushButton:hover { 6 | background-color: rgba(206, 197, 197, 0.21); 7 | } 8 | QPushButton:pressed{ 9 | background-color: rgba(132, 129, 129, 0.264); 10 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/previous_image_button.css: -------------------------------------------------------------------------------- 1 | QPushButton{ 2 | border-image: url('cvplayer/icons/previous2.png'); 3 | background:transparent; 4 | border-radius: 10px; 5 | border: none; 6 | outline: none; 7 | } 8 | QPushButton:hover { 9 | background-color: rgba(206, 197, 197, 0.21); 10 | } 11 | QPushButton:pressed{ 12 | background-color: rgba(132, 129, 129, 0.264); 13 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/print_screen_button.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/stylesheets/print_screen_button.css -------------------------------------------------------------------------------- /cvplayer/stylesheets/video_player_widget.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edu010101/cvplayer/0c1f049175ace1d4255c04937a527f02f37f5883/cvplayer/stylesheets/video_player_widget.css -------------------------------------------------------------------------------- /cvplayer/stylesheets/video_slider.css: -------------------------------------------------------------------------------- 1 | QSlider::sub-page:Horizontal { 2 | background-color: #99eef1; } 3 | 4 | 5 | QSlider::add-page:Horizontal { 6 | background-color: #333333; } 7 | 8 | QSlider::groove:Horizontal { 9 | background: transparent; 10 | height:4px; } 11 | 12 | QSlider::handle:Horizontal { 13 | width:10px; 14 | border-radius:5px; 15 | background:#898989; 16 | margin: -5px 0px -5px 0px; } 17 | 18 | 19 | QSlider { 20 | background-color: rgba(0,0,0,0%); } 21 | -------------------------------------------------------------------------------- /cvplayer/stylesheets/video_speed_button.css: -------------------------------------------------------------------------------- 1 | QSpinBox { 2 | border-radius: 10px; 3 | background-color: rgb(31, 29, 30); 4 | color: white; 5 | font-size: 20px; 6 | font-weight: bold; 7 | padding: 5px 0px 5px 0px; 8 | 9 | 10 | } 11 | 12 | /* background color when line edit is selected */ 13 | 14 | QSpinBox::up-button { 15 | width: 0px; 16 | } 17 | 18 | QSpinBox::down-button { 19 | width: 0px; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /cvplayer/stylesheets/video_time_counter.css: -------------------------------------------------------------------------------- 1 | QLabel{ 2 | background-color:rgba(31,29,30,255); 3 | color: white; 4 | font-size: 20px; 5 | } -------------------------------------------------------------------------------- /cvplayer/stylesheets/videos_list.css: -------------------------------------------------------------------------------- 1 | QComboBox { 2 | border-radius: 7px; 3 | color: lightgray; 4 | background-color: #333333; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | min-width: 350px; 7 | max-width: 350px; 8 | } 9 | QComboBox::hover{ 10 | 11 | background-color: #4e5050; 12 | border-radius: 5px; 13 | } 14 | QComboBox:!editable:on, QComboBox::drop-down:editable:on { 15 | background: #4e5050; 16 | } 17 | QComboBox::drop-down { 18 | subcontrol-origin: padding; 19 | subcontrol-position: top right; 20 | width: 30px; 21 | border-bottom-right-radius: 3px; 22 | border-radius: 5px; 23 | } 24 | QComboBox::down-arrow { 25 | image: url('cvplayer/icons/drop_down.png'); 26 | height: 15px; 27 | width: 15px; 28 | } 29 | QComboBox QAbstractItemView { 30 | border-radius: 5px; 31 | selection-color: #4e5050; 32 | selection-background-color: #4e5050; 33 | color: rgba(91,91,91,255); 34 | background-color: #4e5050; 35 | outline: 0px; 36 | font-size: 12pt; 37 | font-weight: 400; 38 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 39 | } 40 | QListView::item { 41 | height:40px; 42 | outline: 0px; 43 | } 44 | QListView::item:selected { 45 | background-color: #4e5050; 46 | outline: 0px; 47 | } 48 | QScrollBar{ 49 | width:0px; 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /cvplayer/video_player.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QApplication, QMainWindow 2 | from cvplayer.core.media_player.video_player_widget import VideoPlayerWidget 3 | import sys 4 | 5 | class VideoPlayer(): 6 | def __init__(self, custom_class) -> None: 7 | app = QApplication(sys.argv) 8 | if not hasattr(custom_class, 'custom_method') or not callable(getattr(custom_class, 'custom_method')): 9 | raise RuntimeError('custom_method not found in the class') 10 | video_player = VideoPlayerWidget(custom_class) 11 | window = QMainWindow() 12 | window.setStyleSheet('background-color: rgba(31,29,30,255);') 13 | window.setCentralWidget(video_player) 14 | window.setMinimumSize(550,400) 15 | window.show() 16 | sys.exit(app.exec()) 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo_mmdetection.py: -------------------------------------------------------------------------------- 1 | from cvplayer import ImagePlayer 2 | from mmdet.apis import inference_detector, init_detector 3 | 4 | #mmdectection example 5 | class CustomBase(): 6 | def __init__(self) -> None: #always load the model in the constructor 7 | model_config='your_path_to_config/faster_rcnn_r50_fpn_1x.py' #you can use any model from mmdetection 8 | model_weights='your_path_to_weights/epoch_50.pth' 9 | self.detection_model = init_detector(model_config, model_weights, device='cuda:0') 10 | 11 | def custom_method(self, numpy_image): 12 | detection_result = inference_detector(self.detection_model, numpy_image) 13 | return self.detection_model.show_result(numpy_image, detection_result, score_thr=0.7, show=False) 14 | 15 | ImagePlayer(CustomBase()) #pass the class to the ImagePlayer and start the player 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo_yolov8.py: -------------------------------------------------------------------------------- 1 | from cvplayer import VideoPlayer 2 | from ultralytics import YOLO 3 | import cv2 4 | 5 | # yolov8 example 6 | class CustomBase(): 7 | def __init__(self) -> None: #always load the model in the constructor 8 | self.model = YOLO("yolov8s.pt") # load a pretrained model 9 | 10 | def custom_method(self, numpy_image): #method to be called on each frame and do whatever you want(ALLWAYS RECIEVES THE IMAGE AS A NUMPY ARRAY) 11 | results = self.model(numpy_image) # predict on an image 12 | for result in results: 13 | boxes = result.boxes # Boxes object for bbox outputs 14 | classes = result.names 15 | for box in boxes: #just draw the boxes with the class name 16 | cv2.rectangle(numpy_image, (int(box.xyxy[0][0]), int(box.xyxy[0][1])), (int(box.xyxy[0][2]), int(box.xyxy[0][3])), (0, 255, 0), 2) 17 | cv2.putText(numpy_image, classes[int(box.cls)], (int(box.xyxy[0][0]), int(box.xyxy[0][1])), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 4) 18 | return numpy_image #return the image with the changes (ALLWAYS RETURN THE IMAGE AS A NUMPY ARRAY WITH 3 CHANNELS) 19 | 20 | VideoPlayer(CustomBase()) #pass the class to the VideoPlayer and start the player 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyQt6==6.4.2 2 | PyQt6-Qt6==6.4.2 3 | PyQt6-sip==13.4.1 4 | opencv-python 5 | pillow -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='cvplayer', 4 | version='0.1', 5 | packages=['cvplayer'], 6 | ) -------------------------------------------------------------------------------- /tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# cvplayer tutorial\n", 9 | " \n", 10 | "\n", 11 | "In this tutorial we will learn how to use cvplayer to create a video and image player for your model with just a few lines of code.\n", 12 | "\n", 13 | "The repository is divided in 2 main parts:\n", 14 | "\n", 15 | "1. The VideoPlayer\n", 16 | "2. The ImagePlayer\n", 17 | "\n", 18 | "NOTE: Everthing you can do with the VideoPlayer you can do with the ImagePlayer, the only difference is on the declaration." 19 | ] 20 | }, 21 | { 22 | "attachments": {}, 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "## The VideoPlayer !\n", 27 | "1. Is a class that you can use to create a video player that will call your custom method on each frame of the video.\n", 28 | "2. The VideoPlayer can jump to a specific frame, pause, play, change the speed of the video and more.\n", 29 | "3. To use the VideoPlayer you need to create a class that implement the custom_method.\n", 30 | "4. The custom_method is the method that will be called on each frame of the video.\n", 31 | "5. The custom_method must receive a numpy array with 3 channels and return a numpy array with 3 channels.\n", 32 | "\n", 33 | "### Controls\n", 34 | "\n", 35 | "Besides the mouse controls, the VideoPlayer has some keyboard shortcuts:\n", 36 | "\n", 37 | "- Play and pause the video by pressing the space bar.\n", 38 | "- Jump to the next and previous frame by pressing the right and left arrow keys.\n", 39 | "- Change the speed of the video by pressing the up and down arrow keys.\n", 40 | "- Switch and add videos new videos using the videos list and the add videos button.\n", 41 | "\n", 42 | "\n", 43 | "Here is an example with a custom method that just write a random number on the image" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "#import the VideoPlayer class\n", 53 | "from cvplayer import VideoPlayer\n", 54 | "import cv2 #import opencv to write on the image\n", 55 | "import random #import random to generate a random number\n", 56 | "\n", 57 | "class CustomBase(): \n", 58 | " def __init__(self) -> None: #in this case we don't need to initialize anything\n", 59 | " pass\n", 60 | " \n", 61 | " def custom_method(self, numpy_image): #method to be called on each frame and do whatever you want\n", 62 | " #NOTE: the name of the method MUST be custom_method\n", 63 | " #NOTE: the method MUST receive a numpy array with 3 channels and return a numpy array with 3 channels\n", 64 | "\n", 65 | " cv2.putText(numpy_image, str(random.randint(0,100)), (150, 150), cv2.FONT_HERSHEY_SIMPLEX, 5, (255, 0, 0), 10) #write a random number on the image\n", 66 | " \n", 67 | " return numpy_image #return the image with the changes\n", 68 | " \n", 69 | "VideoPlayer(CustomBase()) #pass the class to the VideoPlayer and start the player\n", 70 | "\n" 71 | ] 72 | }, 73 | { 74 | "attachments": {}, 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "## The ImagePlayer !\n", 79 | "1. Is a class that you can use to create a image player that will call your custom method on each image.\n", 80 | "2. The ImagePlayer can jump to a specific image, zoom in, zoom out, and more.\n", 81 | "3. To use the ImagePlayer you need to create a class that implement the custom_method.\n", 82 | "4. The custom_method is the method that will be called on each image.\n", 83 | "5. The custom_method must receive a numpy array with 3 channels and return a numpy array with 3 channels.\n", 84 | "6. The ImagePlayer supports the following image formats: .jpg, .png, .jpeg, .tif, .npy\n", 85 | "7. You can jump to the next and previous image by pressing the right and left arrow keys.\n", 86 | "\n", 87 | "NOTE: The ImagePlayer supports .npy with more than 3 channels, but it will only show the first 3 channels, and your custom method will only receive the first 3 channels.\n", 88 | "\n", 89 | "\n", 90 | "Here is an example with a custom method but now lets use an object detection model to detect objects on the image" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "from cvplayer import ImagePlayer #import the ImagePlayer class\n", 100 | "from ultralytics import YOLO #import the YOLO model\n", 101 | "import cv2 #import opencv to write on the image\n", 102 | "\n", 103 | "# yolov8 example\n", 104 | "class CustomBase(): \n", 105 | " def __init__(self) -> None:\n", 106 | " #NOTE: always initialize your model in the constructor\n", 107 | " self.model = YOLO(\"yolov8s.pt\") # load a pretrained model \n", 108 | " \n", 109 | " def custom_method(self, numpy_image): #method to be called on each frame and do whatever you want\n", 110 | " #NOTE: the name of the method MUST be custom_method\n", 111 | " #NOTE: the method MUST receive a numpy array with 3 channels and return a numpy array with 3 channels\n", 112 | "\n", 113 | " results = self.model(numpy_image) # predict on an image\n", 114 | " for result in results:\n", 115 | " boxes = result.boxes # Boxes object for bbox outputs\n", 116 | " classes = result.names\n", 117 | " for box in boxes: #loop through the boxes and draw them on the image with the class name\n", 118 | " cv2.rectangle(numpy_image, (int(box.xyxy[0][0]), int(box.xyxy[0][1])), (int(box.xyxy[0][2]), int(box.xyxy[0][3])), (0, 255, 0), 2)\n", 119 | " cv2.putText(numpy_image, classes[int(box.cls)], (int(box.xyxy[0][0]), int(box.xyxy[0][1])), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 4)\n", 120 | " \n", 121 | " return numpy_image #return the image with the changes (numpy array with 3 channels)\n", 122 | " \n", 123 | "ImagePlayer(CustomBase()) #pass the class to the ImagePlayer and start the player\n", 124 | "\n" 125 | ] 126 | } 127 | ], 128 | "metadata": { 129 | "kernelspec": { 130 | "display_name": "egetra", 131 | "language": "python", 132 | "name": "python3" 133 | }, 134 | "language_info": { 135 | "codemirror_mode": { 136 | "name": "ipython", 137 | "version": 3 138 | }, 139 | "file_extension": ".py", 140 | "mimetype": "text/x-python", 141 | "name": "python", 142 | "nbconvert_exporter": "python", 143 | "pygments_lexer": "ipython3", 144 | "version": "3.8.15" 145 | }, 146 | "orig_nbformat": 4 147 | }, 148 | "nbformat": 4, 149 | "nbformat_minor": 2 150 | } 151 | --------------------------------------------------------------------------------