First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
142
lenser/imginfo.py
Normal file
142
lenser/imginfo.py
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
"""Collect descriptive metadata about a source image for the on-disk BASIC
|
||||
info program: name, dimensions, format, colour depth, EXIF dates/comments, the
|
||||
file's own date, when the C64 version was made, and the host platform.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import platform
|
||||
|
||||
from PIL import Image
|
||||
|
||||
_DEPTH = {
|
||||
"1": "1 bit mono", "L": "8 bit gray", "LA": "8 bit gray+a",
|
||||
"P": "8 bit palette", "PA": "8 bit pal+a", "RGB": "24 bit rgb",
|
||||
"RGBA": "32 bit rgba", "RGBX": "32 bit rgb", "CMYK": "32 bit cmyk",
|
||||
"YCbCr": "24 bit ycc", "I": "32 bit int", "F": "32 bit float",
|
||||
"I;16": "16 bit gray",
|
||||
}
|
||||
|
||||
# EXIF tag ids.
|
||||
_DATETIME = 306
|
||||
_DATETIME_ORIGINAL = 36867
|
||||
_DATETIME_DIGITIZED = 36868
|
||||
_IMAGE_DESCRIPTION = 270
|
||||
_USER_COMMENT = 37510
|
||||
_XP_COMMENT = 40092
|
||||
_EXIF_IFD = 0x8769
|
||||
|
||||
|
||||
def _fmt_ts(ts) -> str:
|
||||
return datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
|
||||
def _parse_exif_dt(value) -> datetime.datetime | None:
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode("ascii", "ignore")
|
||||
try:
|
||||
return datetime.datetime.strptime(str(value).strip(), "%Y:%m:%d %H:%M:%S")
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
def _decode_comment(value) -> str | None:
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, bytes):
|
||||
raw = value
|
||||
# EXIF UserComment has an 8-byte charset prefix (ASCII / UNICODE / ...).
|
||||
if raw[:8] in (b"ASCII\x00\x00\x00", b"\x00" * 8):
|
||||
raw = raw[8:]
|
||||
elif raw[:8].rstrip(b"\x00") == b"UNICODE":
|
||||
try:
|
||||
return raw[8:].decode("utf-16-be", "ignore").strip("\x00 ") or None
|
||||
except Exception:
|
||||
pass
|
||||
for enc in ("utf-8", "utf-16-le", "latin-1"):
|
||||
try:
|
||||
text = raw.decode(enc, "ignore").strip("\x00 ")
|
||||
if text:
|
||||
return text
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
text = str(value).strip()
|
||||
return text or None
|
||||
|
||||
|
||||
def gather(path: str) -> list[tuple[str, str]]:
|
||||
"""Return an ordered list of (label, value) metadata strings."""
|
||||
fields: list[tuple[str, str]] = []
|
||||
fields.append(("name", os.path.basename(path)))
|
||||
|
||||
img = None
|
||||
try:
|
||||
img = Image.open(path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if img is not None:
|
||||
fields.append(("size", f"{img.width} x {img.height}"))
|
||||
fields.append(("format", img.format or "?"))
|
||||
fields.append(("color", _DEPTH.get(img.mode, img.mode)))
|
||||
|
||||
dates: list[datetime.datetime] = []
|
||||
comment = None
|
||||
if img is not None:
|
||||
try:
|
||||
exif = img.getexif()
|
||||
sub = {}
|
||||
try:
|
||||
sub = exif.get_ifd(_EXIF_IFD)
|
||||
except Exception:
|
||||
sub = {}
|
||||
for tag, src in ((_DATETIME, exif), (_DATETIME_ORIGINAL, sub),
|
||||
(_DATETIME_DIGITIZED, sub)):
|
||||
dt = _parse_exif_dt(src.get(tag))
|
||||
if dt:
|
||||
dates.append(dt)
|
||||
comment = (_decode_comment(sub.get(_USER_COMMENT))
|
||||
or _decode_comment(exif.get(_IMAGE_DESCRIPTION))
|
||||
or _decode_comment(exif.get(_XP_COMMENT)))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if dates:
|
||||
fields.append(("exif date", min(dates).strftime("%Y-%m-%d %H:%M")))
|
||||
|
||||
try:
|
||||
st = os.stat(path)
|
||||
birth = getattr(st, "st_birthtime", None)
|
||||
if birth:
|
||||
fields.append(("file date", _fmt_ts(birth)))
|
||||
else:
|
||||
fields.append(("mod date", _fmt_ts(st.st_mtime)))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if comment:
|
||||
fields.append(("comment", comment))
|
||||
|
||||
fields.append(("c64 made", datetime.datetime.now().strftime("%Y-%m-%d %H:%M")))
|
||||
fields.append(("system", f"{platform.system()} {platform.release()}"))
|
||||
distro = _linux_distro()
|
||||
if distro:
|
||||
fields.append(("distro", distro))
|
||||
return fields
|
||||
|
||||
|
||||
def _linux_distro() -> str | None:
|
||||
"""Linux distribution name + version from /etc/os-release, if available."""
|
||||
try:
|
||||
rel = platform.freedesktop_os_release() # Python 3.10+
|
||||
except (OSError, AttributeError):
|
||||
return None
|
||||
pretty = rel.get("PRETTY_NAME")
|
||||
if pretty:
|
||||
return pretty
|
||||
name = rel.get("NAME", "")
|
||||
version = rel.get("VERSION", rel.get("VERSION_ID", ""))
|
||||
return (f"{name} {version}".strip() or None)
|
||||
Loading…
Add table
Add a link
Reference in a new issue