feat: add camera window
This commit is contained in:
parent
1485c9446c
commit
b6115bbb18
0
draft/camera.py
Normal file
0
draft/camera.py
Normal file
131
draft/pyqt_avif.py
Normal file
131
draft/pyqt_avif.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import io
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from PyQt6.QtGui import QImage, QPixmap
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
|
QGraphicsPixmapItem,
|
||||||
|
QGraphicsScene,
|
||||||
|
QGraphicsView,
|
||||||
|
QMainWindow,
|
||||||
|
QPushButton,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
# For PyQt5 users, change the imports above to this:
|
||||||
|
# from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QVBoxLayout, QWidget, QPushButton, QFileDialog
|
||||||
|
# from PyQt5.QtGui import QPixmap
|
||||||
|
# from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
|
||||||
|
class ZoomableView(QGraphicsView):
|
||||||
|
"""
|
||||||
|
A custom QGraphicsView that provides zoom functionality with the mouse wheel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, scene):
|
||||||
|
super().__init__(scene)
|
||||||
|
# Set anchor points for zooming and resizing
|
||||||
|
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
||||||
|
self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter)
|
||||||
|
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
"""
|
||||||
|
Handles mouse wheel events to zoom in or out.
|
||||||
|
"""
|
||||||
|
# Get the amount of scroll
|
||||||
|
angle = event.angleDelta().y()
|
||||||
|
|
||||||
|
if angle > 0:
|
||||||
|
# Zoom in
|
||||||
|
factor = 1.25
|
||||||
|
else:
|
||||||
|
# Zoom out
|
||||||
|
factor = 0.8
|
||||||
|
|
||||||
|
self.scale(factor, factor)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageViewer(QMainWindow):
|
||||||
|
"""
|
||||||
|
Main application window for viewing an image.
|
||||||
|
Includes loading, zooming, and panning functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.setWindowTitle("PyQt Image Viewer (Load, Zoom, Pan)")
|
||||||
|
self.setGeometry(100, 100, 800, 700)
|
||||||
|
|
||||||
|
# Main widget and layout
|
||||||
|
central_widget = QWidget()
|
||||||
|
self.setCentralWidget(central_widget)
|
||||||
|
layout = QVBoxLayout(central_widget)
|
||||||
|
|
||||||
|
# 1. Create the QGraphicsScene
|
||||||
|
# The scene is the container for all 2D graphical items
|
||||||
|
self.scene = QGraphicsScene()
|
||||||
|
|
||||||
|
# 2. Create the custom QGraphicsView
|
||||||
|
# This is our custom view with zoom capabilities
|
||||||
|
self.view = ZoomableView(self.scene)
|
||||||
|
|
||||||
|
# 3. Enable panning (drag the scene with the mouse)
|
||||||
|
# The hand cursor will appear when you click and drag.
|
||||||
|
self.view.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
||||||
|
|
||||||
|
# Add a button to load an image
|
||||||
|
self.load_button = QPushButton("Load Image")
|
||||||
|
self.load_button.clicked.connect(self.load_image)
|
||||||
|
|
||||||
|
# Add widgets to the layout
|
||||||
|
layout.addWidget(self.load_button)
|
||||||
|
layout.addWidget(self.view)
|
||||||
|
|
||||||
|
# A variable to hold the currently displayed image item
|
||||||
|
self.pixmap_item = None
|
||||||
|
|
||||||
|
def load_image(self):
|
||||||
|
file_path = Path(
|
||||||
|
"/home/lambda/Downloads/f546ed6d35ee05338b8403d57dda10103ac3b1b8.jpg@672w_378h_1c_!web-home-common-cover.avif"
|
||||||
|
)
|
||||||
|
image_data = file_path.read_bytes()
|
||||||
|
im = Image.open(io.BytesIO(image_data))
|
||||||
|
pixmap = QPixmap.fromImage(
|
||||||
|
QImage(im.tobytes(), im.size[0], im.size[1], QImage.Format.Format_RGB888)
|
||||||
|
)
|
||||||
|
if pixmap.isNull():
|
||||||
|
print(f"Error: Failed to load image from {file_path}")
|
||||||
|
return
|
||||||
|
# If an image is already loaded, remove the old one first
|
||||||
|
if self.pixmap_item:
|
||||||
|
self.scene.removeItem(self.pixmap_item)
|
||||||
|
|
||||||
|
# 5. Create a QGraphicsPixmapItem to hold the image
|
||||||
|
self.pixmap_item = QGraphicsPixmapItem(pixmap)
|
||||||
|
|
||||||
|
# 6. Add the item to the scene
|
||||||
|
self.scene.addItem(self.pixmap_item)
|
||||||
|
|
||||||
|
# --- Optional: Improve the viewing experience ---
|
||||||
|
# Reset any previous transformations (like zoom/pan)
|
||||||
|
self.view.resetTransform()
|
||||||
|
# Fit the entire image within the view, maintaining aspect ratio
|
||||||
|
self.view.fitInView(self.pixmap_item, Qt.AspectRatioMode.KeepAspectRatio)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Create the application instance
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# Create and show the main window
|
||||||
|
viewer = ImageViewer()
|
||||||
|
viewer.show()
|
||||||
|
|
||||||
|
# Start the application's event loop
|
||||||
|
sys.exit(app.exec())
|
||||||
@ -1,16 +1,15 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.typing as npt
|
import numpy.typing as npt
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from flandre.utils.archive import to_zip
|
||||||
from flandre.utils.RfFrame import b2t
|
from flandre.utils.RfFrame import b2t
|
||||||
from flandre.utils.RfMeta import RfFrameMeta
|
from flandre.utils.RfMeta import RfFrameMeta
|
||||||
from flandre.utils.archive import to_zip
|
|
||||||
from flandre.utils.robot import rtsi_float2int
|
from flandre.utils.robot import rtsi_float2int
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,7 @@ class LaunchComponent(Enum):
|
|||||||
Mi = auto()
|
Mi = auto()
|
||||||
Web = auto()
|
Web = auto()
|
||||||
VideoQt = auto()
|
VideoQt = auto()
|
||||||
|
CameraQt = auto()
|
||||||
|
|
||||||
|
|
||||||
def launch(arg: dict[LaunchComponent, dict]):
|
def launch(arg: dict[LaunchComponent, dict]):
|
||||||
|
|||||||
133
flandre/nodes/CameraQt.py
Normal file
133
flandre/nodes/CameraQt.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pillow_avif # type: ignore
|
||||||
|
from PIL import Image
|
||||||
|
from PyQt6 import QtCore
|
||||||
|
from PyQt6.QtCore import QByteArray, Qt
|
||||||
|
from PyQt6.QtGui import QImage, QKeyEvent, QPixmap, QResizeEvent, QWheelEvent
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
|
QGraphicsPixmapItem,
|
||||||
|
QGraphicsScene,
|
||||||
|
QMainWindow,
|
||||||
|
)
|
||||||
|
|
||||||
|
from flandre.nodes.Node import Node
|
||||||
|
from flandre.pyqt.Image import Ui_MainWindow
|
||||||
|
from flandre.pyqt.ZMQReceiver import ZMQReceiver
|
||||||
|
from flandre.utils.archive import zip_to_bytes
|
||||||
|
from flandre.utils.Msg import KillMsg, Msg, RfFrameMsg
|
||||||
|
from flandre.utils.RfFrame import RfFrameFile
|
||||||
|
|
||||||
|
pillow_avif.__all__
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Adv(QMainWindow, Ui_MainWindow):
|
||||||
|
ee2 = QtCore.pyqtSignal("QByteArray")
|
||||||
|
|
||||||
|
def __init__(self, p: Node, parent=None):
|
||||||
|
super(Adv, self).__init__(parent)
|
||||||
|
self.p = p
|
||||||
|
self.setupUi(self)
|
||||||
|
zmq_receiver = ZMQReceiver(self)
|
||||||
|
zmq_receiver.zmq_event.connect(self.on_zmq_event)
|
||||||
|
zmq_receiver.start()
|
||||||
|
self.g = QGraphicsPixmapItem()
|
||||||
|
self.s = QGraphicsScene()
|
||||||
|
self.s.addItem(self.g)
|
||||||
|
self.graphicsView.setScene(self.s)
|
||||||
|
self.grey = False
|
||||||
|
self.scale = False
|
||||||
|
self.watermark = True
|
||||||
|
self.zoom = 1.0
|
||||||
|
self.need_fit = False
|
||||||
|
self.frame1: RfFrameFile | None = None
|
||||||
|
self.ee2.connect(self.draw1)
|
||||||
|
threading.Thread(target=self.e1, daemon=True).start()
|
||||||
|
|
||||||
|
def keyPressEvent(self, a0: QKeyEvent):
|
||||||
|
t = a0.text()
|
||||||
|
match t:
|
||||||
|
case "m":
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def resizeEvent(self, a0: QResizeEvent | None) -> None:
|
||||||
|
self.graphicsView.fitInView(
|
||||||
|
self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
|
||||||
|
)
|
||||||
|
|
||||||
|
def wheelEvent(self, a0: QWheelEvent | None) -> None:
|
||||||
|
if a0 is None:
|
||||||
|
return
|
||||||
|
if a0.angleDelta().y() > 0:
|
||||||
|
self.zoom += 0.1
|
||||||
|
if a0.angleDelta().y() < 0:
|
||||||
|
self.zoom -= 0.1
|
||||||
|
|
||||||
|
def draw1(self, d: QByteArray):
|
||||||
|
image_data = d.data()
|
||||||
|
im = Image.open(io.BytesIO(image_data))
|
||||||
|
pixmap = QPixmap.fromImage(
|
||||||
|
QImage(im.tobytes(), im.size[0], im.size[1], QImage.Format.Format_RGB888)
|
||||||
|
)
|
||||||
|
self.g.setPixmap(pixmap)
|
||||||
|
# self.s.setSceneRect(0.0, 0.0, im.size[0], im.size[1])
|
||||||
|
self.graphicsView.fitInView(
|
||||||
|
self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
|
||||||
|
)
|
||||||
|
|
||||||
|
def e1(self):
|
||||||
|
last_frame = self.frame1
|
||||||
|
while True:
|
||||||
|
if last_frame != self.frame1 and self.frame1 is not None:
|
||||||
|
b = zip_to_bytes(
|
||||||
|
self.frame1.seq.path,
|
||||||
|
Path(self.frame1.filename).with_suffix(".avif").__str__(),
|
||||||
|
)
|
||||||
|
self.ee2.emit(b)
|
||||||
|
last_frame = self.frame1
|
||||||
|
|
||||||
|
def on_zmq_event(self, raw_msg: QByteArray):
|
||||||
|
try:
|
||||||
|
msg: Msg = Msg.decode_msg(raw_msg.data())
|
||||||
|
match msg:
|
||||||
|
case KillMsg():
|
||||||
|
if msg.name == "":
|
||||||
|
self.close()
|
||||||
|
case RfFrameMsg():
|
||||||
|
frame = msg.rf_frame
|
||||||
|
match frame:
|
||||||
|
case RfFrameFile():
|
||||||
|
if frame.seq.type == "zip":
|
||||||
|
self.frame1 = frame
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{e}")
|
||||||
|
|
||||||
|
|
||||||
|
class CameraQt(Node):
|
||||||
|
topics = [RfFrameMsg]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
MainWindow = Adv(self)
|
||||||
|
# MainWindow.move(int(px), int(py))
|
||||||
|
# MainWindow.resize(int(sx), int(sy))
|
||||||
|
MainWindow.show()
|
||||||
|
app.exec()
|
||||||
@ -353,7 +353,7 @@ class BMMsg(Msg):
|
|||||||
class RfFrameMsg(HeaderByteMsg):
|
class RfFrameMsg(HeaderByteMsg):
|
||||||
def __init__(self, sender: int, rf_frame: RfFrame):
|
def __init__(self, sender: int, rf_frame: RfFrame):
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
self.rf_frame = rf_frame
|
self.rf_frame: RfFrame = rf_frame
|
||||||
if isinstance(rf_frame, RfFrameFile):
|
if isinstance(rf_frame, RfFrameFile):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
dict(
|
dict(
|
||||||
|
|||||||
@ -28,10 +28,10 @@ class RfFrameFile(RfFrame):
|
|||||||
return self.data
|
return self.data
|
||||||
match self.seq.type:
|
match self.seq.type:
|
||||||
case "zip":
|
case "zip":
|
||||||
from flandre.utils.archive import zip_to_bytes
|
from flandre.utils.archive import zip_to_rfmat
|
||||||
|
|
||||||
if self.filename is not None:
|
if self.filename is not None:
|
||||||
return zip_to_bytes(self.seq.path, int(Path(self.filename).stem))
|
return zip_to_rfmat(self.seq.path, int(Path(self.filename).stem))
|
||||||
case "dir":
|
case "dir":
|
||||||
return (self.seq.path / self.filename).read_bytes()
|
return (self.seq.path / self.filename).read_bytes()
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import subprocess
|
|||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from tqdm import tqdm
|
|
||||||
import zstd
|
import zstd
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
TEMP_FOLDER = Path("/mnt/16T/private_dataset/New Folder/temp")
|
TEMP_FOLDER = Path("/mnt/16T/private_dataset/New Folder/temp")
|
||||||
|
|
||||||
@ -66,12 +66,12 @@ def to_zip(
|
|||||||
subprocess.run(["zip", "-0", "-j", "-r", zipdst, temp_dst])
|
subprocess.run(["zip", "-0", "-j", "-r", zipdst, temp_dst])
|
||||||
|
|
||||||
|
|
||||||
def zip_to_bytes(file: Path, name: int):
|
def zip_to_bytes(file: Path, name: str):
|
||||||
return zstd.loads(zipfile.ZipFile(file).open(f"{name}.zst").read())
|
return zipfile.ZipFile(file).open(name).read()
|
||||||
|
|
||||||
|
|
||||||
def zip_to_bytes2(file: Path, name: str):
|
def zip_to_rfmat(file: Path, frame_index: int):
|
||||||
return zstd.loads(zipfile.ZipFile(file).open(name).read())
|
return zstd.loads(zip_to_bytes(file, f"{frame_index}.zst"))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
|
|
||||||
|
|
||||||
arg_map: dict[str, tuple[int, str]] = dict(
|
arg_map: dict[str, tuple[int, str]] = dict(
|
||||||
x=(100000, "{v:.2f}"),
|
x=(100000, "{v:.2f}"),
|
||||||
y=(100000, "{v:.2f}"),
|
y=(100000, "{v:.2f}"),
|
||||||
|
|||||||
@ -25,6 +25,7 @@ dependencies = [
|
|||||||
"matplotlib>=3.10.1",
|
"matplotlib>=3.10.1",
|
||||||
"fastapi[standard]>=0.115.12",
|
"fastapi[standard]>=0.115.12",
|
||||||
"websockets>=15.0.1",
|
"websockets>=15.0.1",
|
||||||
|
"pillow-avif-plugin>=1.5.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools] # configuration specific to the `setuptools` build backend.
|
[tool.setuptools] # configuration specific to the `setuptools` build backend.
|
||||||
|
|||||||
19
uv.lock
19
uv.lock
@ -490,6 +490,7 @@ dependencies = [
|
|||||||
{ name = "matplotlib" },
|
{ name = "matplotlib" },
|
||||||
{ name = "mido", extra = ["ports-rtmidi"] },
|
{ name = "mido", extra = ["ports-rtmidi"] },
|
||||||
{ name = "opencv-python" },
|
{ name = "opencv-python" },
|
||||||
|
{ name = "pillow-avif-plugin" },
|
||||||
{ name = "platformdirs" },
|
{ name = "platformdirs" },
|
||||||
{ name = "pyjoystick" },
|
{ name = "pyjoystick" },
|
||||||
{ name = "pyqt6" },
|
{ name = "pyqt6" },
|
||||||
@ -521,6 +522,7 @@ requires-dist = [
|
|||||||
{ name = "mido", extras = ["ports-rtmidi"], specifier = ">=1.3.3" },
|
{ name = "mido", extras = ["ports-rtmidi"], specifier = ">=1.3.3" },
|
||||||
{ name = "nvidia-nvimgcodec-cu12", marker = "extra == 'to'", specifier = ">=0.5.0.13" },
|
{ name = "nvidia-nvimgcodec-cu12", marker = "extra == 'to'", specifier = ">=0.5.0.13" },
|
||||||
{ name = "opencv-python", specifier = ">=4.10.0.84" },
|
{ name = "opencv-python", specifier = ">=4.10.0.84" },
|
||||||
|
{ name = "pillow-avif-plugin", specifier = ">=1.5.2" },
|
||||||
{ name = "platformdirs", specifier = ">=4.3.6" },
|
{ name = "platformdirs", specifier = ">=4.3.6" },
|
||||||
{ name = "pyjoystick", specifier = ">=1.2.4" },
|
{ name = "pyjoystick", specifier = ">=1.2.4" },
|
||||||
{ name = "pyqt6", specifier = ">=6.8.0" },
|
{ name = "pyqt6", specifier = ">=6.8.0" },
|
||||||
@ -1331,6 +1333,23 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow-avif-plugin"
|
||||||
|
version = "1.5.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/32/a3bfad0537ba6f2accc6a8a2e53e09b266418347f58898f811ca2fb70bd9/pillow_avif_plugin-1.5.2.tar.gz", hash = "sha256:811e0dc8be1e44393d2e3865ec330a8a8a1194b94eb8cfca6fa778e3f476d649", size = 20571, upload-time = "2025-04-24T14:11:49.163Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/75/0ee9a2a55142183fd881d912bae912df36cf8fe84ba3be92c4530558732f/pillow_avif_plugin-1.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ec28185f536857965be2156e13231274d58a154504db3da4dcda772e391cf5f", size = 3900276, upload-time = "2025-04-24T14:10:32.128Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/7e/f28e53b8c9e31e015c8923ff3dca4c577e38013d4dd5fb005182cd4631a8/pillow_avif_plugin-1.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e36b3cbf5e61b15d2fa213508cf348251e7830ebeff1d97e4e18c55cc0c3b784", size = 2805833, upload-time = "2025-04-24T14:10:33.774Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/f8/3bf477c4d32470819f10c1163c79f3e0a14c9c3f3ffd20d2d051a6a1dd9b/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8098ceed3f76c38b61a35221f0db0205d81d06ff87f4ec11175c90bc66f8c1d", size = 2991435, upload-time = "2025-04-24T14:10:35.06Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/e3/c08f2346d04f0969fec9a6751af8c23dfffd02a55ca91715cb3ee86b53be/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2075069ba6c00236cf7aed66c15863e9ea44b280c20e270aa6f56173b7a380c3", size = 6365698, upload-time = "2025-04-24T14:10:36.678Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/96/7676079b7305a6cc31f284c7036c33f5131886fdc074f204b3b3cdef1886/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:db7d4fc23d8f80ef4f9fdac8e0071856c4c8b9acfc3c4d5cb125d5299d6130b2", size = 3003962, upload-time = "2025-04-24T14:10:37.924Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/34/058a6e5e0794e2fa6e012a350097b989fb0aa9ecc704e9da2cafddbb7a22/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8174501ed895508d5801d61fa9518252693125688dadac7fba79612f9bba623a", size = 4173892, upload-time = "2025-04-24T14:10:39.102Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/52/054bd0c363a5ff6e35d0e763c75cc64eb2408d19a7de34fc6448cdf57fbc/pillow_avif_plugin-1.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:25eeb443636a1c7f0bd0a6994436ad44de09e87648574a797cbd8620b8dcb52d", size = 3101308, upload-time = "2025-04-24T14:10:40.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/83/508f425b4e5e42e6c165b819b4953c482f05b38bc18958b1e11c3bfa7ebb/pillow_avif_plugin-1.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5004a099d52a9a23dfa5b19382a48c0f0930de6231deb013222867fea8948712", size = 4195525, upload-time = "2025-04-24T14:10:41.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/01/0f9cbc4fbb7345790379fb5321776150043d6b6e5674a196a4ba552b570c/pillow_avif_plugin-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:0bc8cec59b5d9c2020cdc6d218081b4d7b0dd60270a24f9174d6b91438a3aa5a", size = 9857569, upload-time = "2025-04-24T14:10:43.099Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "4.3.6"
|
version = "4.3.6"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user