39 lines
1.3 KiB
Python
39 lines
1.3 KiB
Python
"""EXIF-aware dimensions without decoding original photo pixels."""
|
|
|
|
import os
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, UnidentifiedImageError
|
|
|
|
|
|
def dimensions(root: Path, path: Path):
|
|
parent_fd = os.open(root, os.O_RDONLY | os.O_DIRECTORY)
|
|
try:
|
|
parts = path.relative_to(root).parts
|
|
for part in parts[:-1]:
|
|
child = os.open(
|
|
part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent_fd
|
|
)
|
|
os.close(parent_fd)
|
|
parent_fd = child
|
|
fd = os.open(
|
|
parts[-1], os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=parent_fd
|
|
)
|
|
with os.fdopen(fd, "rb") as stream:
|
|
if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
|
|
return None
|
|
with Image.open(stream, formats=("JPEG", "PNG", "WEBP")) as image:
|
|
width, height = image.size
|
|
if image.getexif().get(274, 1) in (5, 6, 7, 8):
|
|
width, height = height, width
|
|
return width, height
|
|
except (OSError, ValueError, UnidentifiedImageError, Image.DecompressionBombError):
|
|
return None
|
|
finally:
|
|
os.close(parent_fd)
|
|
|
|
|
|
def orientation(width, height):
|
|
return "landscape" if width > height else "portrait" if height > width else "square"
|