winipedia-utils 0.1.34__py3-none-any.whl → 0.1.36__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.
- winipedia_utils/pyside/core/__init__.py +1 -0
- winipedia_utils/pyside/core/py_qiodevice.py +93 -0
- winipedia_utils/pyside/ui/pages/player.py +3 -2
- winipedia_utils/pyside/ui/widgets/clickable_widget.py +29 -0
- winipedia_utils/pyside/ui/widgets/media_player.py +53 -39
- winipedia_utils/security/cryptography.py +29 -0
- winipedia_utils/security/keyring.py +11 -7
- {winipedia_utils-0.1.34.dist-info → winipedia_utils-0.1.36.dist-info}/METADATA +1 -1
- {winipedia_utils-0.1.34.dist-info → winipedia_utils-0.1.36.dist-info}/RECORD +11 -7
- {winipedia_utils-0.1.34.dist-info → winipedia_utils-0.1.36.dist-info}/LICENSE +0 -0
- {winipedia_utils-0.1.34.dist-info → winipedia_utils-0.1.36.dist-info}/WHEEL +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
"""__init__ module for winipedia_utils.pyside6.core."""
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""PySide6 QIODevice wrapper."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from PySide6.QtCore import QFile, QIODevice
|
7
|
+
|
8
|
+
|
9
|
+
class PyQIODevice(QIODevice):
|
10
|
+
"""QFile subclass that decrypts data on read."""
|
11
|
+
|
12
|
+
def __init__(self, q_device: QIODevice, *args: Any, **kwargs: Any) -> None:
|
13
|
+
"""Initialize the device."""
|
14
|
+
super().__init__(*args, **kwargs)
|
15
|
+
self.q_device = q_device
|
16
|
+
|
17
|
+
def atEnd(self) -> bool: # noqa: N802
|
18
|
+
"""Check if we are at the end of the file."""
|
19
|
+
return self.q_device.atEnd()
|
20
|
+
|
21
|
+
def bytesAvailable(self) -> int: # noqa: N802
|
22
|
+
"""Return the number of bytes available for reading."""
|
23
|
+
return self.q_device.bytesAvailable()
|
24
|
+
|
25
|
+
def bytesToWrite(self) -> int: # noqa: N802
|
26
|
+
"""Return the number of bytes available for writing."""
|
27
|
+
return self.q_device.bytesToWrite()
|
28
|
+
|
29
|
+
def canReadLine(self) -> bool: # noqa: N802
|
30
|
+
"""Check if we can read a line."""
|
31
|
+
return self.q_device.canReadLine()
|
32
|
+
|
33
|
+
def close(self) -> None:
|
34
|
+
"""Close the device."""
|
35
|
+
self.q_device.close()
|
36
|
+
return super().close()
|
37
|
+
|
38
|
+
def isSequential(self) -> bool: # noqa: N802
|
39
|
+
"""Check if the device is sequential."""
|
40
|
+
return self.q_device.isSequential()
|
41
|
+
|
42
|
+
def open(self, mode: QIODevice.OpenModeFlag) -> bool:
|
43
|
+
"""Open the device."""
|
44
|
+
self.q_device.open(mode)
|
45
|
+
return super().open(mode)
|
46
|
+
|
47
|
+
def pos(self) -> int:
|
48
|
+
"""Return the current position in the device."""
|
49
|
+
return self.q_device.pos()
|
50
|
+
|
51
|
+
def readData(self, maxlen: int) -> bytes: # noqa: N802
|
52
|
+
"""Read data from the device."""
|
53
|
+
return bytes(self.q_device.read(maxlen).data())
|
54
|
+
|
55
|
+
def readLineData(self, maxlen: int) -> object: # noqa: N802
|
56
|
+
"""Read a line from the device."""
|
57
|
+
return self.q_device.readLine(maxlen)
|
58
|
+
|
59
|
+
def reset(self) -> bool:
|
60
|
+
"""Reset the device."""
|
61
|
+
return self.q_device.reset()
|
62
|
+
|
63
|
+
def seek(self, pos: int) -> bool:
|
64
|
+
"""Seek to a position in the device."""
|
65
|
+
return self.q_device.seek(pos)
|
66
|
+
|
67
|
+
def size(self) -> int:
|
68
|
+
"""Return the size of the device."""
|
69
|
+
return self.q_device.size()
|
70
|
+
|
71
|
+
def skipData(self, maxSize: int) -> int: # noqa: N802, N803
|
72
|
+
"""Skip data in the device."""
|
73
|
+
return self.q_device.skip(maxSize)
|
74
|
+
|
75
|
+
def waitForBytesWritten(self, msecs: int) -> bool: # noqa: N802
|
76
|
+
"""Wait for bytes to be written."""
|
77
|
+
return self.q_device.waitForBytesWritten(msecs)
|
78
|
+
|
79
|
+
def waitForReadyRead(self, msecs: int) -> bool: # noqa: N802
|
80
|
+
"""Wait for the device to be ready to read."""
|
81
|
+
return self.q_device.waitForReadyRead(msecs)
|
82
|
+
|
83
|
+
def writeData(self, data: bytes | bytearray | memoryview, len: int) -> int: # noqa: A002, ARG002, N802
|
84
|
+
"""Write data to the device."""
|
85
|
+
return self.q_device.write(data)
|
86
|
+
|
87
|
+
|
88
|
+
class PyQFile(PyQIODevice):
|
89
|
+
"""QFile subclass."""
|
90
|
+
|
91
|
+
def __init__(self, path: Path, *args: Any, **kwargs: Any) -> None:
|
92
|
+
"""Initialize the device."""
|
93
|
+
super().__init__(QFile(path), *args, **kwargs)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
This module contains the player page class for the VideoVault application.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from pathlib import Path
|
6
7
|
from typing import final
|
7
8
|
|
8
9
|
from winipedia_utils.pyside.ui.pages.base.base import Base as BasePage
|
@@ -18,9 +19,9 @@ class Player(BasePage):
|
|
18
19
|
self.media_player = MediaPlayer(self.v_layout)
|
19
20
|
|
20
21
|
@final
|
21
|
-
def
|
22
|
+
def play_file(self, path: Path) -> None:
|
22
23
|
"""Play the video."""
|
23
24
|
# set current page to player
|
24
25
|
self.set_current_page(self.__class__)
|
25
26
|
# Stop current playback and clean up resources
|
26
|
-
self.media_player.
|
27
|
+
self.media_player.play_file(path)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from PySide6.QtCore import Qt, Signal
|
4
|
+
from PySide6.QtMultimediaWidgets import QVideoWidget
|
5
|
+
from PySide6.QtWidgets import QWidget
|
6
|
+
|
7
|
+
|
8
|
+
class ClickableWidget(QWidget):
|
9
|
+
"""Widget that can be clicked."""
|
10
|
+
|
11
|
+
clicked = Signal()
|
12
|
+
|
13
|
+
def mousePressEvent(self, event: Any) -> None: # noqa: N802
|
14
|
+
"""Handle mouse press event."""
|
15
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
16
|
+
self.clicked.emit()
|
17
|
+
super().mousePressEvent(event)
|
18
|
+
|
19
|
+
|
20
|
+
class ClickableVideoWidget(QVideoWidget):
|
21
|
+
"""Video widget that can be clicked."""
|
22
|
+
|
23
|
+
clicked = Signal()
|
24
|
+
|
25
|
+
def mousePressEvent(self, event: Any) -> None: # noqa: N802
|
26
|
+
"""Handle mouse press event."""
|
27
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
28
|
+
self.clicked.emit()
|
29
|
+
super().mousePressEvent(event)
|
@@ -3,12 +3,14 @@
|
|
3
3
|
This module contains the media player class.
|
4
4
|
"""
|
5
5
|
|
6
|
+
import time
|
7
|
+
from collections.abc import Callable
|
6
8
|
from functools import partial
|
9
|
+
from pathlib import Path
|
7
10
|
from typing import Any
|
8
11
|
|
9
|
-
from PySide6.QtCore import
|
12
|
+
from PySide6.QtCore import Qt, QTimer, QUrl
|
10
13
|
from PySide6.QtMultimedia import QAudioOutput, QMediaPlayer
|
11
|
-
from PySide6.QtMultimediaWidgets import QVideoWidget
|
12
14
|
from PySide6.QtWidgets import (
|
13
15
|
QHBoxLayout,
|
14
16
|
QLayout,
|
@@ -20,31 +22,9 @@ from PySide6.QtWidgets import (
|
|
20
22
|
QWidget,
|
21
23
|
)
|
22
24
|
|
25
|
+
from winipedia_utils.pyside.core.py_qiodevice import PyQFile, PyQIODevice
|
23
26
|
from winipedia_utils.pyside.ui.base.base import Base as BaseUI
|
24
|
-
|
25
|
-
|
26
|
-
class ClickableWidget(QWidget):
|
27
|
-
"""Widget that can be clicked."""
|
28
|
-
|
29
|
-
clicked = Signal()
|
30
|
-
|
31
|
-
def mousePressEvent(self, event: Any) -> None: # noqa: N802
|
32
|
-
"""Handle mouse press event."""
|
33
|
-
if event.button() == Qt.MouseButton.LeftButton:
|
34
|
-
self.clicked.emit()
|
35
|
-
super().mousePressEvent(event)
|
36
|
-
|
37
|
-
|
38
|
-
class ClickableVideoWidget(QVideoWidget):
|
39
|
-
"""Video widget that can be clicked."""
|
40
|
-
|
41
|
-
clicked = Signal()
|
42
|
-
|
43
|
-
def mousePressEvent(self, event: Any) -> None: # noqa: N802
|
44
|
-
"""Handle mouse press event."""
|
45
|
-
if event.button() == Qt.MouseButton.LeftButton:
|
46
|
-
self.clicked.emit()
|
47
|
-
super().mousePressEvent(event)
|
27
|
+
from winipedia_utils.pyside.ui.widgets.clickable_widget import ClickableVideoWidget
|
48
28
|
|
49
29
|
|
50
30
|
class MediaPlayer(QMediaPlayer):
|
@@ -228,38 +208,72 @@ class MediaPlayer(QMediaPlayer):
|
|
228
208
|
self.media_controls_layout_below.addWidget(self.progress_slider)
|
229
209
|
|
230
210
|
# Connect media player signals to update the progress slider
|
231
|
-
self.positionChanged.connect(self.
|
232
|
-
self.durationChanged.connect(self.
|
211
|
+
self.positionChanged.connect(self.update_slider_position)
|
212
|
+
self.durationChanged.connect(self.set_slider_range)
|
233
213
|
|
234
214
|
# Connect slider signals to update video position
|
235
|
-
self.
|
236
|
-
self.
|
215
|
+
self.last_slider_moved_update = time.time()
|
216
|
+
self.slider_moved_update_interval = 0.1
|
217
|
+
self.progress_slider.sliderMoved.connect(self.on_slider_moved)
|
218
|
+
self.progress_slider.sliderReleased.connect(self.on_slider_released)
|
237
219
|
|
238
|
-
def
|
220
|
+
def update_slider_position(self, position: int) -> None:
|
239
221
|
"""Update the progress slider position."""
|
240
222
|
# Only update if not being dragged to prevent jumps during manual sliding
|
241
223
|
if not self.progress_slider.isSliderDown():
|
242
224
|
self.progress_slider.setValue(position)
|
243
225
|
|
244
|
-
def
|
226
|
+
def set_slider_range(self, duration: int) -> None:
|
245
227
|
"""Set the progress slider range based on media duration."""
|
246
228
|
self.progress_slider.setRange(0, duration)
|
247
229
|
|
248
|
-
def
|
230
|
+
def on_slider_moved(self, position: int) -> None:
|
249
231
|
"""Set the media position when slider is moved."""
|
232
|
+
current_time = time.time()
|
233
|
+
if (
|
234
|
+
current_time - self.last_slider_moved_update
|
235
|
+
> self.slider_moved_update_interval
|
236
|
+
):
|
237
|
+
self.setPosition(position)
|
238
|
+
self.last_slider_moved_update = current_time
|
250
239
|
self.setPosition(position)
|
251
240
|
|
252
|
-
def
|
241
|
+
def on_slider_released(self) -> None:
|
253
242
|
"""Handle slider release event."""
|
254
243
|
self.setPosition(self.progress_slider.value())
|
255
244
|
|
256
|
-
def
|
245
|
+
def play_video(
|
246
|
+
self, set_source_func: Callable[..., Any], *args: Any, **kwargs: Any
|
247
|
+
) -> None:
|
257
248
|
"""Play the video."""
|
258
249
|
self.stop()
|
259
|
-
self.buffer = QBuffer()
|
260
|
-
self.buffer.setData(QByteArray(data))
|
261
|
-
self.buffer.open(QBuffer.OpenModeFlag.ReadOnly)
|
262
250
|
|
263
|
-
|
251
|
+
# prevents freezing when starting a new video while another is playing
|
252
|
+
QTimer.singleShot(
|
253
|
+
100, partial(self.set_source_and_play, set_source_func, *args, **kwargs)
|
254
|
+
)
|
264
255
|
|
256
|
+
def set_source_and_play(
|
257
|
+
self, set_source_func: Callable[..., Any], *args: Any, **kwargs: Any
|
258
|
+
) -> None:
|
259
|
+
"""Set the source and play the video."""
|
260
|
+
set_source_func(*args, **kwargs)
|
265
261
|
self.play()
|
262
|
+
|
263
|
+
def play_file(self, path: Path) -> None:
|
264
|
+
"""Play the video."""
|
265
|
+
self.play_video(
|
266
|
+
self.set_source_device,
|
267
|
+
io_device=PyQFile(path),
|
268
|
+
source_url=QUrl.fromLocalFile(path),
|
269
|
+
)
|
270
|
+
|
271
|
+
def set_source_device(
|
272
|
+
self, io_device: PyQIODevice, source_url: QUrl | None = None
|
273
|
+
) -> None:
|
274
|
+
"""Play the video."""
|
275
|
+
if source_url is None:
|
276
|
+
source_url = QUrl()
|
277
|
+
self.source_url = source_url
|
278
|
+
self.io_device = io_device
|
279
|
+
self.setSourceDevice(self.io_device, self.source_url)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""Cryptography utilities for secure data handling.
|
2
|
+
|
3
|
+
This module provides utility functions for working with cryptography,
|
4
|
+
including encryption and decryption using Fernet and AESGCM.
|
5
|
+
These utilities help with secure data handling.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
|
10
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
11
|
+
|
12
|
+
IV_LEN = 12
|
13
|
+
|
14
|
+
|
15
|
+
def encrypt_with_aes_gcm(
|
16
|
+
aes_gcm: AESGCM, data: bytes, aad: bytes | None = None
|
17
|
+
) -> bytes:
|
18
|
+
"""Encrypt data using AESGCM."""
|
19
|
+
iv = os.urandom(IV_LEN)
|
20
|
+
encrypted = aes_gcm.encrypt(iv, data, aad)
|
21
|
+
return iv + encrypted
|
22
|
+
|
23
|
+
|
24
|
+
def decrypt_with_aes_gcm(
|
25
|
+
aes_gcm: AESGCM, data: bytes, aad: bytes | None = None
|
26
|
+
) -> bytes:
|
27
|
+
"""Decrypt data using AESGCM."""
|
28
|
+
iv, encrypted = data[:IV_LEN], data[IV_LEN:]
|
29
|
+
return aes_gcm.decrypt(iv, encrypted, aad)
|
@@ -7,9 +7,10 @@ These utilities help with secure storage and retrieval of secrets.
|
|
7
7
|
|
8
8
|
import keyring
|
9
9
|
from cryptography.fernet import Fernet
|
10
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
10
11
|
|
11
12
|
|
12
|
-
def
|
13
|
+
def get_or_create_fernet(service_name: str, username: str) -> Fernet:
|
13
14
|
"""Get the app secret using keyring.
|
14
15
|
|
15
16
|
If it does not exist, create it with a Fernet.
|
@@ -18,13 +19,16 @@ def get_or_create_secret(service_name: str, username: str) -> str:
|
|
18
19
|
if secret is None:
|
19
20
|
secret = Fernet.generate_key().decode()
|
20
21
|
keyring.set_password(service_name, username, secret)
|
21
|
-
return secret
|
22
|
+
return Fernet(secret.encode())
|
22
23
|
|
23
24
|
|
24
|
-
def
|
25
|
-
"""Get the app
|
25
|
+
def get_or_create_aes_gcm(service_name: str, username: str) -> AESGCM:
|
26
|
+
"""Get the app secret using keyring.
|
26
27
|
|
27
|
-
If it does not exist, create it with a
|
28
|
+
If it does not exist, create it with a AESGCM.
|
28
29
|
"""
|
29
|
-
secret =
|
30
|
-
|
30
|
+
secret = keyring.get_password(service_name, username)
|
31
|
+
if secret is None:
|
32
|
+
secret = AESGCM.generate_key(bit_length=256).decode()
|
33
|
+
keyring.set_password(service_name, username, secret)
|
34
|
+
return AESGCM(secret.encode())
|
@@ -41,6 +41,8 @@ winipedia_utils/projects/poetry/poetry.py,sha256=5jyUSMxhCZ7pz9bOaz5E9r7Da9qIrGO
|
|
41
41
|
winipedia_utils/projects/project.py,sha256=2nz1Hh51A-shjgdPCgiDw-ODrVtOtiHEHQnMPjAJZ-A,1587
|
42
42
|
winipedia_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
43
43
|
winipedia_utils/pyside/__init__.py,sha256=knliQxknKWqxCEfxlI1K02OtbBfnTkYUZFqyYcLJNDI,52
|
44
|
+
winipedia_utils/pyside/core/__init__.py,sha256=5VmASY9rUuZ8_PeZt8KMqnZUGxhsnE0EKbDhCIXj6Gk,57
|
45
|
+
winipedia_utils/pyside/core/py_qiodevice.py,sha256=YPBeEyLJizQPdb-c45XKmmqUIKe3iFZyJQHdgp8Lqfk,3182
|
44
46
|
winipedia_utils/pyside/ui/__init__.py,sha256=h5NJ6WyveMMqxIgt0x65_mqndtncxgNmx5816KD3ubU,55
|
45
47
|
winipedia_utils/pyside/ui/base/__init__.py,sha256=p-maJQh7gYbXwhkqKU4wL6UNGzRAy988JP8_qLoTZDk,24
|
46
48
|
winipedia_utils/pyside/ui/base/base.py,sha256=ru-CC-y9KQCHqkVHWGmV7ovIe4D149vBjpsNRPyoVAY,4146
|
@@ -48,10 +50,11 @@ winipedia_utils/pyside/ui/pages/__init__.py,sha256=p-maJQh7gYbXwhkqKU4wL6UNGzRAy
|
|
48
50
|
winipedia_utils/pyside/ui/pages/base/__init__.py,sha256=p-maJQh7gYbXwhkqKU4wL6UNGzRAy988JP8_qLoTZDk,24
|
49
51
|
winipedia_utils/pyside/ui/pages/base/base.py,sha256=9pt0Hsewo2q_sMu6xI5grF0dcoazRL73EiML8zKOzuE,2059
|
50
52
|
winipedia_utils/pyside/ui/pages/browser.py,sha256=SzX356snnmEdzCrtIXl5mPaUkM1-_REfUurOotpVIkY,696
|
51
|
-
winipedia_utils/pyside/ui/pages/player.py,sha256=
|
53
|
+
winipedia_utils/pyside/ui/pages/player.py,sha256=PPmUNejMibKeNUm-a-1VA6i14G743YFxuxem0NZzFz4,795
|
52
54
|
winipedia_utils/pyside/ui/widgets/__init__.py,sha256=p-maJQh7gYbXwhkqKU4wL6UNGzRAy988JP8_qLoTZDk,24
|
53
55
|
winipedia_utils/pyside/ui/widgets/browser.py,sha256=5oUyQKYOYu9Zzx6adbewTtAIfnn_RVs8CLoX3rU2g28,6160
|
54
|
-
winipedia_utils/pyside/ui/widgets/
|
56
|
+
winipedia_utils/pyside/ui/widgets/clickable_widget.py,sha256=nUZ7ngPFQ7utJuq9A-72TeT1wqCt_xubAxBg3xQSL64,856
|
57
|
+
winipedia_utils/pyside/ui/widgets/media_player.py,sha256=uTHmdJyiUQBOlZsAVse2CAZG-m6RcMODMX45SbryQYs,11040
|
55
58
|
winipedia_utils/pyside/ui/widgets/notification.py,sha256=_zBq9Q6SDFu3fV2AkbvyCNQmOEOy1l9mKOFAAdyteg4,1941
|
56
59
|
winipedia_utils/pyside/ui/windows/__init__.py,sha256=p-maJQh7gYbXwhkqKU4wL6UNGzRAy988JP8_qLoTZDk,24
|
57
60
|
winipedia_utils/pyside/ui/windows/base/__init__.py,sha256=p-maJQh7gYbXwhkqKU4wL6UNGzRAy988JP8_qLoTZDk,24
|
@@ -65,7 +68,8 @@ winipedia_utils/resources/svgs/pause_icon.svg,sha256=mNrEoAOhbvxsJmBg4xZ08kdahAG
|
|
65
68
|
winipedia_utils/resources/svgs/play_icon.svg,sha256=U0RQ-S_WbcyyjeWC55BkcOtG64GfXALXlnB683foZUY,416
|
66
69
|
winipedia_utils/resources/svgs/svg.py,sha256=-Dw6m7cm9CHT2076oZIMx7kTQw0v_ifJajXzWcpUtI0,430
|
67
70
|
winipedia_utils/security/__init__.py,sha256=ZBa72J6MNtYumBFMoVc0ia4jsoS7oNgjaTCW0xDb6EI,53
|
68
|
-
winipedia_utils/security/
|
71
|
+
winipedia_utils/security/cryptography.py,sha256=zfxSDo7aE9ecmZNC6URMSEUYRpOuJ1iESg-WCSS5HP0,822
|
72
|
+
winipedia_utils/security/keyring.py,sha256=eJifLGKnlOF_9tB3y6fvHXxr02a-TWOXljb1JI6ztkU,1218
|
69
73
|
winipedia_utils/setup.py,sha256=F4NneO0wVTf7JCXLorWjTOdJl36N5fLSksoWMe4p86o,1650
|
70
74
|
winipedia_utils/testing/__init__.py,sha256=kXhB5xw02ec5xpcW_KV--9CBKdyCjnuR-NZzAJ5tq0g,51
|
71
75
|
winipedia_utils/testing/assertions.py,sha256=0JF4mqVTnLQ1qkAL_FuTwyN_idr00rvVlta7aDdnUXA,851
|
@@ -87,7 +91,7 @@ winipedia_utils/testing/tests/base/utils/utils.py,sha256=dUPDrgAxlfREQb33zz23Mfz
|
|
87
91
|
winipedia_utils/testing/tests/conftest.py,sha256=8RounBlI8Jq1aLaLNpv84MW4ne8Qq0aavQextDOp5ng,920
|
88
92
|
winipedia_utils/text/__init__.py,sha256=j2bwtK6kyeHI6SnoBjpRju0C1W2n2paXBDlNjNtaUxA,48
|
89
93
|
winipedia_utils/text/string.py,sha256=1jbBftlgxffGgSlPnQh3aRPIr8XekEwpSenjFCW6JyM,3478
|
90
|
-
winipedia_utils-0.1.
|
91
|
-
winipedia_utils-0.1.
|
92
|
-
winipedia_utils-0.1.
|
93
|
-
winipedia_utils-0.1.
|
94
|
+
winipedia_utils-0.1.36.dist-info/LICENSE,sha256=3PrKJ2CWNrnyyHaC_r0wPDSukVWgmjOxHr__eQVH7cw,1087
|
95
|
+
winipedia_utils-0.1.36.dist-info/METADATA,sha256=dombJeW2JBlMRgVubedOvgrWclo7vxbJ4MWIJeh-fMw,12576
|
96
|
+
winipedia_utils-0.1.36.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
97
|
+
winipedia_utils-0.1.36.dist-info/RECORD,,
|
File without changes
|
File without changes
|