Working Python version for Commodore.

This commit is contained in:
The Dust Council 2026-06-14 17:43:12 -07:00
commit 2a48f52979
51 changed files with 3095 additions and 0 deletions

142
c64view/imginfo.py Normal file
View 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)