winipedia-utils 0.1.38__py3-none-any.whl → 0.1.40__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,11 +14,17 @@ class Browser(BasePage):
14
14
 
15
15
  @final
16
16
  def setup(self) -> None:
17
- """Setup the UI."""
18
- # add a download button in the top right
17
+ """Setup the UI.
18
+
19
+ Initializes the browser page by adding a browser widget to the layout.
20
+ """
19
21
  self.add_brwoser()
20
22
 
21
23
  @final
22
24
  def add_brwoser(self) -> None:
23
- """Add a browser to surfe the web."""
25
+ """Add a browser to surf the web.
26
+
27
+ Creates and adds a BrowserWidget instance to the vertical layout,
28
+ enabling web browsing functionality within the page.
29
+ """
24
30
  self.browser = BrowserWidget(self.v_layout)
@@ -3,8 +3,12 @@
3
3
  This module contains the player page class for the VideoVault application.
4
4
  """
5
5
 
6
+ from abc import abstractmethod
7
+ from collections.abc import Callable
6
8
  from pathlib import Path
7
- from typing import final
9
+ from typing import Any, final
10
+
11
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
8
12
 
9
13
  from winipedia_utils.pyside.ui.pages.base.base import Base as BasePage
10
14
  from winipedia_utils.pyside.ui.widgets.media_player import MediaPlayer
@@ -13,15 +17,58 @@ from winipedia_utils.pyside.ui.widgets.media_player import MediaPlayer
13
17
  class Player(BasePage):
14
18
  """Player page for the VideoVault application."""
15
19
 
20
+ @abstractmethod
21
+ def start_playback(self, path: Path) -> None:
22
+ """Start the playback.
23
+
24
+ Args:
25
+ path: The file path to start playback for.
26
+ """
27
+
16
28
  @final
17
29
  def setup(self) -> None:
18
- """Setup the UI."""
30
+ """Setup the UI.
31
+
32
+ Initializes the media player widget and adds it to the layout.
33
+ """
19
34
  self.media_player = MediaPlayer(self.v_layout)
20
35
 
21
36
  @final
22
- def play_file(self, path: Path) -> None:
23
- """Play the video."""
37
+ def play_file_from_func(
38
+ self, play_func: Callable[..., Any], path: Path, **kwargs: Any
39
+ ) -> None:
40
+ """Play a file using the specified function.
41
+
42
+ Sets the current page to player and calls the provided play function
43
+ with the given path and additional arguments.
44
+
45
+ Args:
46
+ play_func: The function to call for playing the file.
47
+ path: The file path to play.
48
+ **kwargs: Additional keyword arguments to pass to the play function.
49
+ """
24
50
  # set current page to player
25
51
  self.set_current_page(self.__class__)
26
52
  # Stop current playback and clean up resources
27
- self.media_player.play_file(path)
53
+ play_func(path, **kwargs)
54
+
55
+ @final
56
+ def play_file(self, path: Path) -> None:
57
+ """Play a regular video file.
58
+
59
+ Args:
60
+ path: The file path to play.
61
+ """
62
+ self.play_file_from_func(self.media_player.play_file, path)
63
+
64
+ @final
65
+ def play_encrypted_file(self, path: Path, aes_gcm: AESGCM) -> None:
66
+ """Play an encrypted video file.
67
+
68
+ Args:
69
+ path: The encrypted file path to play.
70
+ aes_gcm: The AES-GCM cipher instance for decryption.
71
+ """
72
+ self.play_file_from_func(
73
+ self.media_player.play_encrypted_file, path, aes_gcm=aes_gcm
74
+ )
@@ -26,7 +26,13 @@ class Browser(QWebEngineView):
26
26
  """Browser class that creates a simple ready to use browser and not just a view."""
27
27
 
28
28
  def __init__(self, parent_layout: QLayout, *args: Any, **kwargs: Any) -> None:
29
- """Initialize the browser."""
29
+ """Initialize the browser.
30
+
31
+ Args:
32
+ parent_layout: The parent layout to add the browser widget to.
33
+ *args: Additional positional arguments passed to parent constructor.
34
+ **kwargs: Additional keyword arguments passed to parent constructor.
35
+ """
30
36
  super().__init__(*args, **kwargs)
31
37
  self.parent_layout = parent_layout
32
38
  self.make_widget()
@@ -34,7 +40,11 @@ class Browser(QWebEngineView):
34
40
  self.load_first_url()
35
41
 
36
42
  def make_address_bar(self) -> None:
37
- """Make the address bar."""
43
+ """Make the address bar.
44
+
45
+ Creates a horizontal layout containing back button, forward button,
46
+ address input field, and go button for browser navigation.
47
+ """
38
48
  self.address_bar_layout = QHBoxLayout()
39
49
 
40
50
  # Add back button
@@ -65,12 +75,19 @@ class Browser(QWebEngineView):
65
75
  self.browser_layout.addLayout(self.address_bar_layout)
66
76
 
67
77
  def navigate_to_url(self) -> None:
68
- """Navigate to the URL entered in the address bar."""
78
+ """Navigate to the URL entered in the address bar.
79
+
80
+ Takes the URL from the address bar text field and loads it in the browser.
81
+ """
69
82
  url = self.address_bar.text()
70
83
  self.load(QUrl(url))
71
84
 
72
85
  def make_widget(self) -> None:
73
- """Make the widget."""
86
+ """Make the widget.
87
+
88
+ Creates the main browser widget with vertical layout, sets size policy,
89
+ creates the address bar, and adds components to the parent layout.
90
+ """
74
91
  self.browser_widget = QWidget()
75
92
  self.browser_layout = QVBoxLayout(self.browser_widget)
76
93
  self.set_size_policy()
@@ -79,53 +96,99 @@ class Browser(QWebEngineView):
79
96
  self.parent_layout.addWidget(self.browser_widget)
80
97
 
81
98
  def set_size_policy(self) -> None:
82
- """Set the size policy."""
99
+ """Set the size policy.
100
+
101
+ Sets the browser to expand in both horizontal and vertical directions.
102
+ """
83
103
  self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
84
104
 
85
105
  def connect_signals(self) -> None:
86
- """Connect the signals."""
106
+ """Connect the signals.
107
+
108
+ Connects load finished signal and cookie added signal handlers.
109
+ """
87
110
  self.connect_load_finished_signal()
88
111
  self.connect_on_cookie_added_signal()
89
112
 
90
113
  def connect_load_finished_signal(self) -> None:
91
- """Connect the load finished signal."""
92
- self.loadFinished.connect(self.on_load_finished)
114
+ """Connect the load finished signal.
93
115
 
94
- def on_load_finished(self, _ok: bool) -> None: # noqa: FBT001
95
- """Handle the load finished signal."""
116
+ Connects the loadStarted signal to the on_load_started handler.
117
+ """
118
+ self.loadStarted.connect(self.on_load_started)
119
+
120
+ def on_load_started(self, _ok: bool) -> None: # noqa: FBT001
121
+ """Handle the load finished signal.
122
+
123
+ Args:
124
+ _ok: Boolean indicating if the load was successful (unused).
125
+ """
96
126
  self.update_address_bar(self.url())
97
127
 
98
128
  def update_address_bar(self, url: QUrl) -> None:
99
- """Update the address bar with the current URL."""
129
+ """Update the address bar with the current URL.
130
+
131
+ Args:
132
+ url: The QUrl to display in the address bar.
133
+ """
100
134
  self.address_bar.setText(url.toString())
101
135
 
102
136
  def connect_on_cookie_added_signal(self) -> None:
103
- """Connect the on cookie added signal."""
137
+ """Connect the on cookie added signal.
138
+
139
+ Initializes the cookies dictionary and connects the cookieAdded signal
140
+ to the on_cookie_added handler.
141
+ """
104
142
  self.cookies: dict[str, list[QNetworkCookie]] = defaultdict(list)
105
143
  self.page().profile().cookieStore().cookieAdded.connect(self.on_cookie_added)
106
144
 
107
145
  def on_cookie_added(self, cookie: Any) -> None:
108
- """Handle the on cookie added signal."""
146
+ """Handle the on cookie added signal.
147
+
148
+ Args:
149
+ cookie: The QNetworkCookie that was added.
150
+ """
109
151
  self.cookies[cookie.domain()].append(cookie)
110
152
 
111
153
  def load_first_url(self) -> None:
112
- """Load the first URL."""
154
+ """Load the first URL.
155
+
156
+ Loads Google's homepage as the initial page when the browser starts.
157
+ """
113
158
  self.load(QUrl("https://www.google.com/"))
114
159
 
115
160
  @property
116
161
  def http_cookies(self) -> dict[str, list[Cookie]]:
117
- """Get the http cookies for the given URL."""
162
+ """Get the http cookies for all domains.
163
+
164
+ Returns:
165
+ Dictionary mapping domain names to lists of http.cookiejar.Cookie objects.
166
+ """
118
167
  return {
119
168
  domain: self.qcookies_to_httpcookies(qcookies)
120
169
  for domain, qcookies in self.cookies.items()
121
170
  }
122
171
 
123
172
  def qcookies_to_httpcookies(self, qcookies: list[QNetworkCookie]) -> list[Cookie]:
124
- """Convert a list of QNetworkCookies to a CookieJar."""
173
+ """Convert a list of QNetworkCookies to http.cookiejar.Cookie objects.
174
+
175
+ Args:
176
+ qcookies: List of QNetworkCookie objects to convert.
177
+
178
+ Returns:
179
+ List of converted http.cookiejar.Cookie objects.
180
+ """
125
181
  return [self.qcookie_to_httpcookie(q_cookie) for q_cookie in qcookies]
126
182
 
127
183
  def qcookie_to_httpcookie(self, qcookie: QNetworkCookie) -> Cookie:
128
- """Convert a QNetworkCookie to a http.cookiejar.Cookie."""
184
+ """Convert a QNetworkCookie to a http.cookiejar.Cookie.
185
+
186
+ Args:
187
+ qcookie: The QNetworkCookie to convert.
188
+
189
+ Returns:
190
+ The converted http.cookiejar.Cookie object.
191
+ """
129
192
  name = bytes(qcookie.name().data()).decode()
130
193
  value = bytes(qcookie.value().data()).decode()
131
194
  domain = qcookie.domain()
@@ -157,10 +220,24 @@ class Browser(QWebEngineView):
157
220
  )
158
221
 
159
222
  def get_domain_cookies(self, domain: str) -> list[QNetworkCookie]:
160
- """Get the cookies for the given domain."""
223
+ """Get the cookies for the given domain.
224
+
225
+ Args:
226
+ domain: The domain to get cookies for.
227
+
228
+ Returns:
229
+ List of QNetworkCookie objects for the specified domain.
230
+ """
161
231
  return self.cookies[domain]
162
232
 
163
233
  def get_domain_http_cookies(self, domain: str) -> list[Cookie]:
164
- """Get the http cookies for the given domain."""
234
+ """Get the http cookies for the given domain.
235
+
236
+ Args:
237
+ domain: The domain to get cookies for.
238
+
239
+ Returns:
240
+ List of http.cookiejar.Cookie objects for the specified domain.
241
+ """
165
242
  cookies = self.get_domain_cookies(domain)
166
243
  return self.qcookies_to_httpcookies(cookies)
@@ -1,3 +1,9 @@
1
+ """Clickable widget module.
2
+
3
+ This module contains clickable widget classes that emit signals when clicked.
4
+ Provides both regular QWidget and QVideoWidget variants with click functionality.
5
+ """
6
+
1
7
  from typing import Any
2
8
 
3
9
  from PySide6.QtCore import Qt, Signal
@@ -6,24 +12,46 @@ from PySide6.QtWidgets import QWidget
6
12
 
7
13
 
8
14
  class ClickableWidget(QWidget):
9
- """Widget that can be clicked."""
15
+ """Widget that can be clicked.
16
+
17
+ A QWidget subclass that emits a clicked signal when the left mouse
18
+ button is pressed on the widget.
19
+ """
10
20
 
11
21
  clicked = Signal()
12
22
 
13
23
  def mousePressEvent(self, event: Any) -> None: # noqa: N802
14
- """Handle mouse press event."""
24
+ """Handle mouse press event.
25
+
26
+ Emits the clicked signal when the left mouse button is pressed
27
+ and passes the event to the parent class.
28
+
29
+ Args:
30
+ event: The mouse press event containing button and position information.
31
+ """
15
32
  if event.button() == Qt.MouseButton.LeftButton:
16
33
  self.clicked.emit()
17
34
  super().mousePressEvent(event)
18
35
 
19
36
 
20
37
  class ClickableVideoWidget(QVideoWidget):
21
- """Video widget that can be clicked."""
38
+ """Video widget that can be clicked.
39
+
40
+ A QVideoWidget subclass that emits a clicked signal when the left mouse
41
+ button is pressed on the video widget.
42
+ """
22
43
 
23
44
  clicked = Signal()
24
45
 
25
46
  def mousePressEvent(self, event: Any) -> None: # noqa: N802
26
- """Handle mouse press event."""
47
+ """Handle mouse press event.
48
+
49
+ Emits the clicked signal when the left mouse button is pressed
50
+ and passes the event to the parent class.
51
+
52
+ Args:
53
+ event: The mouse press event containing button and position information.
54
+ """
27
55
  if event.button() == Qt.MouseButton.LeftButton:
28
56
  self.clicked.emit()
29
57
  super().mousePressEvent(event)
@@ -9,6 +9,7 @@ from functools import partial
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
12
13
  from PySide6.QtCore import Qt, QTimer, QUrl
13
14
  from PySide6.QtMultimedia import QAudioOutput, QMediaPlayer
14
15
  from PySide6.QtWidgets import (
@@ -22,7 +23,11 @@ from PySide6.QtWidgets import (
22
23
  QWidget,
23
24
  )
24
25
 
25
- from winipedia_utils.pyside.core.py_qiodevice import PyQFile, PyQIODevice
26
+ from winipedia_utils.pyside.core.py_qiodevice import (
27
+ EncryptedPyQFile,
28
+ PyQFile,
29
+ PyQIODevice,
30
+ )
26
31
  from winipedia_utils.pyside.ui.base.base import Base as BaseUI
27
32
  from winipedia_utils.pyside.ui.widgets.clickable_widget import ClickableVideoWidget
28
33
 
@@ -31,13 +36,23 @@ class MediaPlayer(QMediaPlayer):
31
36
  """Media player class."""
32
37
 
33
38
  def __init__(self, parent_layout: QLayout, *args: Any, **kwargs: Any) -> None:
34
- """Initialize the media player."""
39
+ """Initialize the media player.
40
+
41
+ Args:
42
+ parent_layout: The parent layout to add the media player widget to.
43
+ *args: Additional positional arguments passed to parent constructor.
44
+ **kwargs: Additional keyword arguments passed to parent constructor.
45
+ """
35
46
  super().__init__(*args, **kwargs)
36
47
  self.parent_layout = parent_layout
37
48
  self.make_widget()
38
49
 
39
50
  def make_widget(self) -> None:
40
- """Make the widget."""
51
+ """Make the widget.
52
+
53
+ Creates the main media player widget with vertical layout, adds media controls
54
+ above and below the video widget, and creates the video widget itself.
55
+ """
41
56
  self.media_player_widget = QWidget()
42
57
  self.media_player_layout = QVBoxLayout(self.media_player_widget)
43
58
  self.parent_layout.addWidget(self.media_player_widget)
@@ -46,7 +61,11 @@ class MediaPlayer(QMediaPlayer):
46
61
  self.add_media_controls_below()
47
62
 
48
63
  def make_video_widget(self) -> None:
49
- """Make the video widget."""
64
+ """Make the video widget.
65
+
66
+ Creates a clickable video widget with expanding size policy, sets up
67
+ audio output, and connects the click signal to toggle media controls.
68
+ """
50
69
  self.video_widget = ClickableVideoWidget()
51
70
  self.video_widget.clicked.connect(self.on_video_clicked)
52
71
  self.video_widget.setSizePolicy(
@@ -60,24 +79,37 @@ class MediaPlayer(QMediaPlayer):
60
79
  self.media_player_layout.addWidget(self.video_widget)
61
80
 
62
81
  def on_video_clicked(self) -> None:
63
- """Handle video widget click."""
82
+ """Handle video widget click.
83
+
84
+ Toggles the visibility of media controls when the video widget is clicked.
85
+ """
64
86
  if self.media_controls_widget_above.isVisible():
65
87
  self.hide_media_controls()
66
88
  return
67
89
  self.show_media_controls()
68
90
 
69
91
  def show_media_controls(self) -> None:
70
- """Show media controls."""
92
+ """Show media controls.
93
+
94
+ Makes both the above and below media control widgets visible.
95
+ """
71
96
  self.media_controls_widget_above.show()
72
97
  self.media_controls_widget_below.show()
73
98
 
74
99
  def hide_media_controls(self) -> None:
75
- """Hide media controls."""
100
+ """Hide media controls.
101
+
102
+ Hides both the above and below media control widgets.
103
+ """
76
104
  self.media_controls_widget_above.hide()
77
105
  self.media_controls_widget_below.hide()
78
106
 
79
107
  def add_media_controls_above(self) -> None:
80
- """Add media controls above the video."""
108
+ """Add media controls above the video.
109
+
110
+ Creates the top control bar with left, center, and right sections,
111
+ then adds speed, volume, playback, and fullscreen controls.
112
+ """
81
113
  # main above widget
82
114
  self.media_controls_widget_above = QWidget()
83
115
  self.media_controls_layout_above = QHBoxLayout(self.media_controls_widget_above)
@@ -106,14 +138,21 @@ class MediaPlayer(QMediaPlayer):
106
138
  self.add_fullscreen_control()
107
139
 
108
140
  def add_media_controls_below(self) -> None:
109
- """Add media controls below the video."""
141
+ """Add media controls below the video.
142
+
143
+ Creates the bottom control bar and adds the progress control slider.
144
+ """
110
145
  self.media_controls_widget_below = QWidget()
111
146
  self.media_controls_layout_below = QHBoxLayout(self.media_controls_widget_below)
112
147
  self.media_player_layout.addWidget(self.media_controls_widget_below)
113
148
  self.add_progress_control()
114
149
 
115
150
  def add_playback_control(self) -> None:
116
- """Add playback control."""
151
+ """Add playback control.
152
+
153
+ Creates a play/pause button with appropriate icons and connects it
154
+ to the toggle_playback method. Adds the button to the center controls.
155
+ """
117
156
  self.play_icon = BaseUI.get_svg_icon("play_icon")
118
157
  self.pause_icon = BaseUI.get_svg_icon("pause_icon")
119
158
  # Pause symbol: ⏸ (U+23F8)
@@ -124,7 +163,11 @@ class MediaPlayer(QMediaPlayer):
124
163
  self.center_controls_layout.addWidget(self.playback_button)
125
164
 
126
165
  def toggle_playback(self) -> None:
127
- """Toggle playback."""
166
+ """Toggle playback.
167
+
168
+ Switches between play and pause states, updating the button icon
169
+ accordingly based on the current playback state.
170
+ """
128
171
  if self.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
129
172
  self.pause()
130
173
  self.playback_button.setIcon(self.play_icon)
@@ -135,7 +178,8 @@ class MediaPlayer(QMediaPlayer):
135
178
  def add_speed_control(self) -> None:
136
179
  """Add speed control.
137
180
 
138
- A button in the top left that on click shows a dropdown to select the speed.
181
+ Creates a button in the top left that shows a dropdown menu to select
182
+ playback speed from predefined options (0.2x to 5x).
139
183
  """
140
184
  self.default_speed = 1
141
185
  self.speed_options = [0.2, 0.5, self.default_speed, 1.5, 2, 3, 4, 5]
@@ -149,24 +193,40 @@ class MediaPlayer(QMediaPlayer):
149
193
  self.left_controls_layout.addWidget(self.speed_button)
150
194
 
151
195
  def change_speed(self, speed: float) -> None:
152
- """Change playback speed."""
196
+ """Change playback speed.
197
+
198
+ Args:
199
+ speed: The new playback speed multiplier.
200
+ """
153
201
  self.setPlaybackRate(speed)
154
202
  self.speed_button.setText(f"{speed}x")
155
203
 
156
204
  def add_volume_control(self) -> None:
157
- """Add volume control."""
205
+ """Add volume control.
206
+
207
+ Creates a horizontal slider for volume control with range 0-100
208
+ and connects it to the volume change handler.
209
+ """
158
210
  self.volume_slider = QSlider(Qt.Orientation.Horizontal)
159
211
  self.volume_slider.setRange(0, 100)
160
212
  self.volume_slider.valueChanged.connect(self.on_volume_changed)
161
213
  self.left_controls_layout.addWidget(self.volume_slider)
162
214
 
163
215
  def on_volume_changed(self, value: int) -> None:
164
- """Handle volume slider value change."""
216
+ """Handle volume slider value change.
217
+
218
+ Args:
219
+ value: The new volume value from 0-100.
220
+ """
165
221
  volume = value / 100.0 # Convert to 0.0-1.0 range
166
222
  self.audio_output.setVolume(volume)
167
223
 
168
224
  def add_fullscreen_control(self) -> None:
169
- """Add fullscreen control."""
225
+ """Add fullscreen control.
226
+
227
+ Creates a fullscreen toggle button with appropriate icons and determines
228
+ which widgets to hide/show when entering/exiting fullscreen mode.
229
+ """
170
230
  self.fullscreen_icon = BaseUI.get_svg_icon("fullscreen_icon")
171
231
  self.exit_fullscreen_icon = BaseUI.get_svg_icon("exit_fullscreen_icon")
172
232
  self.fullscreen_button = QPushButton()
@@ -187,7 +247,11 @@ class MediaPlayer(QMediaPlayer):
187
247
  self.right_controls_layout.addWidget(self.fullscreen_button)
188
248
 
189
249
  def toggle_fullscreen(self) -> None:
190
- """Toggle fullscreen mode."""
250
+ """Toggle fullscreen mode.
251
+
252
+ Switches between fullscreen and windowed mode, hiding/showing other
253
+ widgets and updating the button icon accordingly.
254
+ """
191
255
  # Get the main window
192
256
  main_window = self.media_player_widget.window()
193
257
  if main_window.isFullScreen():
@@ -203,7 +267,11 @@ class MediaPlayer(QMediaPlayer):
203
267
  self.fullscreen_button.setIcon(self.exit_fullscreen_icon)
204
268
 
205
269
  def add_progress_control(self) -> None:
206
- """Add progress control."""
270
+ """Add progress control.
271
+
272
+ Creates a horizontal progress slider and connects it to media player
273
+ signals for position updates and user interaction handling.
274
+ """
207
275
  self.progress_slider = QSlider(Qt.Orientation.Horizontal)
208
276
  self.media_controls_layout_below.addWidget(self.progress_slider)
209
277
 
@@ -218,17 +286,32 @@ class MediaPlayer(QMediaPlayer):
218
286
  self.progress_slider.sliderReleased.connect(self.on_slider_released)
219
287
 
220
288
  def update_slider_position(self, position: int) -> None:
221
- """Update the progress slider position."""
289
+ """Update the progress slider position.
290
+
291
+ Args:
292
+ position: The current media position in milliseconds.
293
+ """
222
294
  # Only update if not being dragged to prevent jumps during manual sliding
223
295
  if not self.progress_slider.isSliderDown():
224
296
  self.progress_slider.setValue(position)
225
297
 
226
298
  def set_slider_range(self, duration: int) -> None:
227
- """Set the progress slider range based on media duration."""
299
+ """Set the progress slider range based on media duration.
300
+
301
+ Args:
302
+ duration: The total media duration in milliseconds.
303
+ """
228
304
  self.progress_slider.setRange(0, duration)
229
305
 
230
306
  def on_slider_moved(self, position: int) -> None:
231
- """Set the media position when slider is moved."""
307
+ """Set the media position when slider is moved.
308
+
309
+ Implements throttling to prevent excessive position updates during
310
+ slider dragging for better performance.
311
+
312
+ Args:
313
+ position: The new position from the slider in milliseconds.
314
+ """
232
315
  current_time = time.time()
233
316
  if (
234
317
  current_time - self.last_slider_moved_update
@@ -238,13 +321,25 @@ class MediaPlayer(QMediaPlayer):
238
321
  self.last_slider_moved_update = current_time
239
322
 
240
323
  def on_slider_released(self) -> None:
241
- """Handle slider release event."""
324
+ """Handle slider release event.
325
+
326
+ Sets the final media position when the user releases the slider.
327
+ """
242
328
  self.setPosition(self.progress_slider.value())
243
329
 
244
330
  def play_video(
245
331
  self, set_source_func: Callable[..., Any], *args: Any, **kwargs: Any
246
332
  ) -> None:
247
- """Play the video."""
333
+ """Play the video.
334
+
335
+ Stops current playback and starts a new video using the provided
336
+ source function with a delay to prevent freezing.
337
+
338
+ Args:
339
+ set_source_func: Function to call for setting the video source.
340
+ *args: Additional positional arguments for the source function.
341
+ **kwargs: Additional keyword arguments for the source function.
342
+ """
248
343
  self.stop()
249
344
 
250
345
  # prevents freezing when starting a new video while another is playing
@@ -255,20 +350,48 @@ class MediaPlayer(QMediaPlayer):
255
350
  def set_source_and_play(
256
351
  self, set_source_func: Callable[..., Any], *args: Any, **kwargs: Any
257
352
  ) -> None:
258
- """Set the source and play the video."""
353
+ """Set the source and play the video.
354
+
355
+ Args:
356
+ set_source_func: Function to call for setting the video source.
357
+ *args: Additional positional arguments for the source function.
358
+ **kwargs: Additional keyword arguments for the source function.
359
+ """
259
360
  set_source_func(*args, **kwargs)
260
361
  self.play()
261
362
 
363
+ def set_source_device(self, io_device: PyQIODevice, source_url: QUrl) -> None:
364
+ """Set the source device for playback.
365
+
366
+ Args:
367
+ io_device: The PyQIODevice to use as the media source.
368
+ source_url: The QUrl representing the source location.
369
+ """
370
+ self.source_url = source_url
371
+ self.io_device = io_device
372
+ self.setSourceDevice(self.io_device, self.source_url)
373
+
262
374
  def play_file(self, path: Path) -> None:
263
- """Play the video."""
375
+ """Play a regular video file.
376
+
377
+ Args:
378
+ path: The file path to the video file to play.
379
+ """
264
380
  self.play_video(
265
381
  self.set_source_device,
266
382
  io_device=PyQFile(path),
267
383
  source_url=QUrl.fromLocalFile(path),
268
384
  )
269
385
 
270
- def set_source_device(self, io_device: PyQIODevice, source_url: QUrl) -> None:
271
- """Play the video."""
272
- self.source_url = source_url
273
- self.io_device = io_device
274
- self.setSourceDevice(self.io_device, self.source_url)
386
+ def play_encrypted_file(self, path: Path, aes_gcm: AESGCM) -> None:
387
+ """Play an encrypted video file.
388
+
389
+ Args:
390
+ path: The file path to the encrypted video file to play.
391
+ aes_gcm: The AES-GCM cipher instance for decryption.
392
+ """
393
+ self.play_video(
394
+ self.set_source_device,
395
+ io_device=EncryptedPyQFile(path, aes_gcm),
396
+ source_url=QUrl.fromLocalFile(path),
397
+ )