Core Package
The winipyside.src.core package provides advanced QIODevice wrappers
for PySide6, including transparent AES-GCM encryption
for files and media playback.
Overview
The core package contains three main classes:
PyQIODevice- Python-friendly wrapper around Qt's QIODevicePyQFile- File-specific wrapper with Path supportEncryptedPyQFile- Transparent AES-GCM encrypted file I/O
PyQIODevice
A Python-friendly wrapper around PySide6's QIODevice that provides enhanced functionality and easier integration with Python code.
Class Definition
class PyQIODevice(QIODevice):
"""PySide6 QIODevice wrapper with enhanced functionality."""
def __init__(self, q_device: QIODevice, *args: Any, **kwargs: Any) -> None:
"""Initialize the PyQIODevice wrapper.
Args:
q_device: The QIODevice instance to wrap.
"""
Key Features
- Wraps any QIODevice instance
- Provides Python-friendly interface
- Maintains all original QIODevice functionality
- Seamless integration with PySide6's I/O system
Methods
readData(maxlen: int) -> bytes
Read data from the device.
Parameters:
maxlen(int): Maximum number of bytes to read
Returns:
bytes: The data read from the device
writeData(data: bytes) -> int
Write data to the device.
Parameters:
data(bytes): The data to write
Returns:
int: Number of bytes written
size() -> int
Get the size of the device.
Returns:
int: Size in bytes
pos() -> int
Get the current position in the device.
Returns:
int: Current position in bytes
seek(pos: int) -> bool
Seek to a specific position.
Parameters:
pos(int): Position to seek to
Returns:
bool: True if successful
Example
from PySide6.QtCore import QFile, QIODevice
from winipyside.src.core.py_qiodevice import PyQIODevice
# Wrap a QFile
qfile = QFile("example.txt")
device = PyQIODevice(qfile)
device.open(QIODevice.OpenModeFlag.ReadOnly)
# Read data
data = device.readData(1024)
print(data)
device.close()
PyQFile
A file-specific wrapper extending PyQIODevice with simplified file operations and Path support.
Class Definition
class PyQFile(PyQIODevice):
"""QFile wrapper with enhanced Python integration."""
def __init__(self, path: Path, *args: Any, **kwargs: Any) -> None:
"""Initialize the PyQFile with a file path.
Args:
path: The file path to open.
"""
Key Features
- Accepts
pathlib.Pathobjects - Simplified file operations
- Compatible with Qt's media framework
- Automatic QFile creation
Example
from pathlib import Path
from PySide6.QtCore import QIODevice, QUrl
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from winipyside.src.core.py_qiodevice import PyQFile
# Open a video file
video_path = Path("video.mp4")
file = PyQFile(video_path)
file.open(QIODevice.OpenModeFlag.ReadOnly)
# Use with QMediaPlayer
player = QMediaPlayer()
player.setAudioOutput(QAudioOutput())
player.setSourceDevice(file, QUrl.fromLocalFile(str(video_path)))
player.play()
EncryptedPyQFile
Transparent AES-GCM encrypted file I/O with support for streaming and random access.
Class Definition
class EncryptedPyQFile(PyQFile):
"""Encrypted file wrapper with AES-GCM encryption."""
def __init__(
self, path: Path, aes_gcm: AESGCM, *args: Any, **kwargs: Any
) -> None:
"""Initialize the encrypted file wrapper.
Args:
path: The file path to open.
aes_gcm: The AES-GCM cipher instance for encryption/decryption.
"""
Key Features
- Chunked Encryption: 64KB cipher chunks for efficient streaming
- Random Access: Position mapping between encrypted and decrypted data
- Authenticated Encryption: AES-GCM with 12-byte nonces and 16-byte tags
- Zero-Copy Decryption: Works seamlessly with QMediaPlayer
- Static Methods: Standalone encryption/decryption operations
Encryption Constants
NONCE_SIZE = 12 # 12-byte nonce for AES-GCM
TAG_SIZE = 16 # 16-byte authentication tag
CIPHER_SIZE = 64 * 1024 # 64KB cipher chunks
CHUNK_SIZE = NONCE_SIZE + CIPHER_SIZE + TAG_SIZE # 65564 bytes total
Methods
readData(maxlen: int) -> bytes
Read and decrypt data from the encrypted file.
Parameters:
maxlen(int): Maximum number of decrypted bytes to read
Returns:
bytes: Decrypted data
How it works:
- Reads encrypted chunks from file
- Decrypts each chunk with AES-GCM
- Returns requested portion of decrypted data
size() -> int
Get the decrypted size of the file.
Returns:
int: Size of decrypted data in bytes
seek(pos: int) -> bool
Seek to a position in the decrypted data.
Parameters:
pos(int): Position in decrypted data
Returns:
bool: True if successful
How it works:
- Maps decrypted position to encrypted position
- Seeks to start of corresponding encrypted chunk
- Maintains offset within chunk for next read
Static Methods
get_encrypted_pos(decrypted_pos: int) -> int
Map a decrypted position to its corresponding encrypted position.
Parameters:
decrypted_pos(int): Position in decrypted data
Returns:
int: Corresponding position in encrypted data
get_decrypted_pos(encrypted_pos: int) -> int
Map an encrypted position to its corresponding decrypted position.
Parameters:
encrypted_pos(int): Position in encrypted data
Returns:
int: Corresponding position in decrypted data
encrypt_file(...) -> None
Encrypt a file using AES-GCM.
Parameters:
input_path(Path): Path to input fileoutput_path(Path): Path to output encrypted fileaes_gcm(AESGCM): AES-GCM cipher instance
Example:
from pathlib import Path
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from winipyside.src.core.py_qiodevice import EncryptedPyQFile
# Generate key
key = AESGCM.generate_key(bit_length=256)
aes_gcm = AESGCM(key)
# Encrypt file
EncryptedPyQFile.encrypt_file(
Path("video.mp4"),
Path("video_encrypted.mp4"),
aes_gcm
)
decrypt_file(...) -> None
Decrypt a file encrypted with AES-GCM.
Parameters:
input_path(Path): Path to encrypted fileoutput_path(Path): Path to output decrypted fileaes_gcm(AESGCM): AES-GCM cipher instance
Example:
# Decrypt file
EncryptedPyQFile.decrypt_file(
Path("video_encrypted.mp4"),
Path("video_decrypted.mp4"),
aes_gcm
)
Encrypted Video Playback
The primary use case for EncryptedPyQFile
is playing encrypted videos without creating temporary decrypted files.
Complete Example:
from pathlib import Path
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from PySide6.QtCore import QUrl, QIODevice
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from PySide6.QtMultimediaWidgets import QVideoWidget
from winipyside.src.core.py_qiodevice import EncryptedPyQFile
# Generate or load encryption key
key = AESGCM.generate_key(bit_length=256)
aes_gcm = AESGCM(key)
# Create application
app = QApplication([])
window = QMainWindow()
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
window.setCentralWidget(widget)
# Create video widget
video_widget = QVideoWidget()
layout.addWidget(video_widget)
# Create media player
player = QMediaPlayer()
player.setAudioOutput(QAudioOutput())
player.setVideoOutput(video_widget)
# Open encrypted file
video_path = Path("encrypted_video.mp4")
encrypted_file = EncryptedPyQFile(video_path, aes_gcm)
encrypted_file.open(QIODevice.OpenModeFlag.ReadOnly)
# Play encrypted video
player.setSourceDevice(encrypted_file, QUrl.fromLocalFile(str(video_path)))
player.play()
window.show()
app.exec()
How Encryption Works
Chunk Structure
Each encrypted chunk has the following structure:
[Nonce (12 bytes)][Encrypted Data (64KB)][Auth Tag (16 bytes)]
Total chunk size: 65,564 bytes
Encryption Process
- Read plaintext chunk (64KB)
- Generate random nonce (12 bytes)
- Encrypt with AES-GCM using nonce and optional AAD
- Write to file: nonce + ciphertext + tag
Decryption Process
- Read encrypted chunk (65,564 bytes)
- Extract nonce (first 12 bytes)
- Extract ciphertext + tag (remaining bytes)
- Decrypt with AES-GCM using nonce
- Return plaintext
Position Mapping
To support random access (seeking), positions must be mapped between encrypted and decrypted data:
Decrypted → Encrypted:
chunk_index = decrypted_pos // CIPHER_SIZE
encrypted_pos = chunk_index * CHUNK_SIZE
Encrypted → Decrypted:
chunk_index = encrypted_pos // CHUNK_SIZE
decrypted_pos = chunk_index * CIPHER_SIZE
Best Practices
Key Management
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# Generate a new key
key = AESGCM.generate_key(bit_length=256)
# Save key securely (example - use proper key management in production)
with open("encryption.key", "wb") as f:
f.write(key)
# Load key
with open("encryption.key", "rb") as f:
key = f.read()
aes_gcm = AESGCM(key)
Resource Management
Always close files when done:
encrypted_file = EncryptedPyQFile(path, aes_gcm)
try:
encrypted_file.open(QIODevice.OpenModeFlag.ReadOnly)
# Use file
player.setSourceDevice(encrypted_file, url)
finally:
# File will be closed when player is done
pass
Performance Considerations
- Chunk size: 64KB is optimized for streaming video
- Random access: Seeking requires reading from chunk boundaries
- Memory usage: Only one chunk is decrypted at a time
- CPU usage: AES-GCM is hardware-accelerated on modern CPUs
See Also
- UI Widgets - MediaPlayer - Using encrypted files with MediaPlayer
- Examples - Encrypted Video Player
- API Reference