"""
This module provides a VideoController class responsible for controlling video playback in a PyQt6 application.
It handles events such as play/pause, stop, rewind, and forward, as well as updating the video timer and slider
position. The VideoController interacts with the PyQt6 GUI elements and the underlying video model to manage
video playback functionalities.
"""
import cv2
from PyQt6 import QtGui
[docs]
class VideoController:
"""
This class manages the control logic for video playback in a PyQt6 application. It handles events such as play/pause,
stop, rewind, and forward, as well as updating the video timer and slider position. The VideoController interacts
with the PyQt6 GUI elements and the underlying video model to coordinate video playback functionalities.
Attributes:
_controller: The controller object managing the application.
_model: The model object containing application data and settings.
_model_video_control: The video configuration model containing video-specific settings.
_ui_object: The user interface object containing PyQt6 elements.
"""
def __init__(self, controller):
try:
self._controller = controller
self._model = controller.model
self._model_video_control = controller.model.video_config
self._ui_object = controller.ui_object
self.check_active_play_pause_button()
self._connect_event()
except Exception as e:
self._model.activity_logger.error(f"VideoController.__init__(): Error initializing VideoController: {str(e)}")
def _connect_event(self):
try:
self._ui_object.play_pause_button.clicked.connect(self._onclick_play_pause_video_button)
self._ui_object.play_pause_button.setShortcut("Space")
self._ui_object.rewind_button.clicked.connect(self._rewind_video_5_second)
self._ui_object.rewind_button.setShortcut("Left")
self._ui_object.stop_button.clicked.connect(self._stop_video)
self._ui_object.stop_button.setShortcut("0")
self._ui_object.forward_button.clicked.connect(self._forward_video_5_second)
self._ui_object.forward_button.setShortcut("Right")
self._ui_object.slider_video_time.valueChanged.connect(self._slider_controller)
except Exception as e:
self._model.activity_logger.error(f"VideoController._connect_event(): Error connecting events: {str(e)}")
def _onclick_play_pause_video_button(self):
"""
Handle the click event of the play/pause button.
Toggle between playing and pausing the video.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info("VideoController: _onclick_play_pause_video_button(), Onclick action "
"play pause video button")
self._play_pause_video()
self.check_active_play_pause_button()
except Exception as e:
self._model.activity_logger.error(f"VideoController._onclick_play_pause_video_button(): Error: {str(e)}")
def _play_pause_video(self):
"""
Toggle between playing and pausing the video.
If the video player timer is currently active, this method will pause the video. If the timer is not active,
the method will resume playing the video. Additionally, if the video player was previously displaying a saved
image, the method will reset the player to resume playing the video.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info("VideoController._play_pause_video(): Action play pause video button")
if self._controller.timer.isActive():
self._controller.timer.stop()
else:
self._model.load_saved_image = False
if self._model_video_control.fps_video is not None:
self._controller.timer.start(round(1000 / self._model_video_control.fps_video))
else:
self._controller.timer.start()
except Exception as e:
self._model.activity_logger.error(f"VideoController._play_pause_video(): Error: {str(e)}")
def _stop_video(self):
"""
Stop the video player by resetting the video capture position to the beginning,
stopping the timer, and updating the play/pause button icon.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info("VideoController._stop_video(): Stop video player")
if self._controller.cap is not None:
if self._model_video_control.fps_video is not None:
self._controller.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
self._controller.media_next_frame_signal()
self._controller.timer.stop()
self.check_active_play_pause_button()
except Exception as e:
self._model.activity_logger.error(f"VideoController._stop_video(): Error: {str(e)}")
def _rewind_video_5_second(self):
"""
Rewind a video by 5 seconds from the current position.
If a video is loaded, this function calculates the position of the video 5 seconds before the current position,
and updates the video to that position. If the calculated position is before the beginning of the video, the
function sets the video to the first frame. If no video is loaded, the function does nothing.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info("VideoController._rewind_video_5_second(): Rewind video 5 seconds")
if self._controller.cap is not None:
if self._model_video_control.fps_video is not None:
position = self._model_video_control.pos_frame_video - 5 * self._model_video_control.fps_video
self._controller.cap.set(cv2.CAP_PROP_POS_FRAMES, position)
self._controller.media_next_frame_signal()
except Exception as e:
self._model.activity_logger.error(f"VideoController._rewind_video_5_second(): Error: {str(e)}")
def _forward_video_5_second(self):
"""
Update the position of a video based on the value of a slider widget.
If a video is loaded, this function calculates the frame number corresponding to the value of a slider widget
as a percentage of the total duration, and updates the video to that frame. If no video is loaded, the function
does nothing.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info("VideoController._forward_video_5_second(): Forward video 5 seconds")
if self._controller.cap is not None:
if self._model_video_control.fps_video is not None:
position = self._model_video_control.pos_frame_video + 5 * self._model_video_control.fps_video
if position > self._model_video_control.total_frame_video:
self._controller.cap.set(cv2.CAP_PROP_POS_FRAMES,
self._model_video_control.total_frame_video - 1)
else:
self._controller.cap.set(cv2.CAP_PROP_POS_FRAMES, position)
self._controller.media_next_frame_signal()
except Exception as e:
self._model.activity_logger.error(f"VideoController._forward_video_5_second(): Error: {str(e)}")
def _slider_controller(self, value):
"""
Change the value of the slider video controller.
Arg:
value (int): The new value of the slider.
Return:
None
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info(
"VideoController._slider_controller(): Change value slider video controller")
if self._controller.cap is not None:
if self._model_video_control.fps_video is not None:
dst_frame = self._model_video_control.total_frame_video * value / 100
self._controller.cap.set(cv2.CAP_PROP_POS_FRAMES, dst_frame)
self._controller.media_next_frame_signal()
except Exception as e:
self._model.activity_logger.error(f"VideoController._slider_controller(): Error: {str(e)}")
[docs]
def show_timer_video_info(self):
"""
Update the video timer labels with the given time values.
This method takes a list parameter, list_timer, that contains four values representing the current and
total times of the video in minutes and seconds. The current time is displayed in the label_current_time label,
and the total time is displayed in the label_total_time label.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info(
"VideoController.show_timer_video_info(): Show value slider video controller to user interface")
list_timer = self._model_video_control.video_time
if list_timer[0] is not None:
current_time = f"{list_timer[2]:02d}:{list_timer[3]:02d}"
total_time = f"{list_timer[0]:02d}:{list_timer[1]:02d}"
self._ui_object.label_current_time.setText(current_time)
self._ui_object.label_total_time.setText(total_time)
except Exception as e:
self._model.activity_logger.error(f"VideoController.show_timer_video_info(): Error: {str(e)}")
[docs]
def set_slider_video_time_position(self):
"""
Set the value of a slider widget based on the current position of a video.
If a video is loaded, this function calculates the current time position as a percentage of the total duration
and emits a signal with the corresponding value to update a slider widget. If no video is loaded, the function
does nothing.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info(
"VideoController.set_slider_video_time_position(): Calculate video frame time related to full "
"video source")
if self._controller.cap is not None:
if self._model_video_control.fps_video is not None:
dst_value = (self._model_video_control.pos_frame_video * 100 /
self._model_video_control.total_frame_video)
self.set_slider_position(dst_value)
except Exception as e:
self._model.activity_logger.error(f"VideoController.set_slider_video_time_position(): Error: {str(e)}")
[docs]
def set_slider_position(self, value):
"""
Set the position of the video slider.
This method takes a float parameter, value, that represents the position of the video slider.
The slider is updated to the given value, and the blockSignals method is used to prevent signals
from being emitted during the update.
"""
try:
if self._model.debug_mode:
self._model.activity_logger.info(
"VideoController.set_slider_position(): Set slider position depend on time of video")
self._ui_object.slider_video_time.blockSignals(True)
self._ui_object.slider_video_time.setValue(int(value))
self._ui_object.slider_video_time.blockSignals(False)
except Exception as e:
self._model.activity_logger.error(f"VideoController.set_slider_position(): Error: {str(e)}")