mirror of https://github.com/wanadev/yoga.git
121 lines
3.2 KiB
Python
121 lines
3.2 KiB
Python
import io
|
|
import struct
|
|
|
|
|
|
def little_endian_unint32_bytes_to_python_int(bytes_):
|
|
return struct.unpack("<L", bytes_)[0]
|
|
|
|
|
|
def get_riff_structure(data):
|
|
if data[0:4] != b"RIFF":
|
|
raise ValueError("Invalid RIFF: Not a RIFF file")
|
|
|
|
result = {
|
|
"formtype": data[8:12].decode(),
|
|
"size": little_endian_unint32_bytes_to_python_int(data[4:8]),
|
|
"chunks": [],
|
|
}
|
|
|
|
if result["size"] + 8 != len(data):
|
|
raise ValueError("Invalid RIFF: Truncated data")
|
|
|
|
offset = 12 # RIFF header length
|
|
|
|
while offset < len(data):
|
|
chunk = {
|
|
"type": data[offset : offset + 4].decode(),
|
|
"data_offset": offset + 8,
|
|
"size": little_endian_unint32_bytes_to_python_int(
|
|
data[offset + 4 : offset + 8]
|
|
),
|
|
}
|
|
result["chunks"].append(chunk)
|
|
offset += 8 + chunk["size"] + chunk["size"] % 2
|
|
|
|
return result
|
|
|
|
|
|
def get_vp8x_info(data):
|
|
# fmt: off
|
|
# RRILEXAR
|
|
VP8X_FLAG_ICC = 0b00100000 # noqa: E221
|
|
VP8X_FLAG_ALPHA = 0b00010000 # noqa: E221
|
|
VP8X_FLAG_EXIF = 0b00001000 # noqa: E221
|
|
VP8X_FLAG_XMP = 0b00000100 # noqa: E221
|
|
VP8X_FLAG_ANIM = 0b00000010 # noqa: E221
|
|
# fmt: on
|
|
|
|
if len(data) != 10:
|
|
ValueError("Invalid VP8X data")
|
|
|
|
return {
|
|
"has_icc": bool(data[0] & VP8X_FLAG_ICC),
|
|
"has_alpha": bool(data[0] & VP8X_FLAG_ALPHA),
|
|
"has_exif": bool(data[0] & VP8X_FLAG_EXIF),
|
|
"has_xmp": bool(data[0] & VP8X_FLAG_XMP),
|
|
"has_anim": bool(data[0] & VP8X_FLAG_ANIM),
|
|
"canvas_width": little_endian_unint32_bytes_to_python_int(
|
|
data[4:7] + b"\x00"
|
|
)
|
|
+ 1,
|
|
"canvas_height": little_endian_unint32_bytes_to_python_int(
|
|
data[7:10] + b"\x00"
|
|
)
|
|
+ 1,
|
|
}
|
|
|
|
|
|
def is_riff(file_bytes):
|
|
"""Whether or not the given bytes represent a RIFF file.
|
|
|
|
:params bytes file_bytes: The bytes of the file to check.
|
|
|
|
:rtype: bool
|
|
:return: ``True`` if the bytes represent a RIFF file, ``False`` else.
|
|
"""
|
|
return file_bytes.startswith(b"RIFF")
|
|
|
|
|
|
def is_lossy_webp(file_bytes):
|
|
"""Whether or not the given bytes represent a lossy WEBP file.
|
|
|
|
:params bytes file_bytes: The bytes of the file to check.
|
|
|
|
:rtype: bool
|
|
:return: ``True`` if the bytes represent a lossy WEBP file, ``False`` else.
|
|
"""
|
|
if not is_riff(file_bytes):
|
|
return False
|
|
|
|
riff = get_riff_structure(file_bytes)
|
|
|
|
if riff["formtype"] == "WEBP":
|
|
chunks = [chunk["type"] for chunk in riff["chunks"]]
|
|
if "VP8 " in chunks:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def optimize_lossy_webp(image, quality):
|
|
"""Encode image to lossy WEBP using Pillow.
|
|
|
|
:param PIL.Image image: The image to encode.
|
|
:param float quality: The output WEBP quality (from ``0.00``to ``1.00``).
|
|
|
|
:returns: The encoded image's bytes.
|
|
"""
|
|
if not 0.00 <= quality <= 1.00:
|
|
raise ValueError("WEBP quality value must be between 0.00 and 1.00")
|
|
|
|
image_io = io.BytesIO()
|
|
image.save(
|
|
image_io,
|
|
format="WEBP",
|
|
lossless=False,
|
|
quality=int(quality * 100),
|
|
method=6,
|
|
)
|
|
image_io.seek(0)
|
|
return image_io.read()
|