Initial working thingy
This commit is contained in:
parent
0ccf4cb603
commit
80f720c418
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,3 +2,6 @@
|
||||
result
|
||||
# Vim swap files
|
||||
*.swp
|
||||
# Generated website files
|
||||
build
|
||||
dist
|
||||
|
48
Makefile
Normal file
48
Makefile
Normal file
@ -0,0 +1,48 @@
|
||||
.PHONY: all, clean
|
||||
|
||||
ifeq ($(origin IMAGE_DIR),undefined)
|
||||
$(error $$IMAGE_DIR is not set)
|
||||
endif
|
||||
|
||||
# Files for distribution
|
||||
DIST_DIR ?= dist
|
||||
# Temporary build artifacts
|
||||
BUILD_DIR ?= build
|
||||
|
||||
images := $(shell find $(IMAGE_DIR) -iname '*.jpg')
|
||||
image_names_cs := $(notdir $(images))
|
||||
image_names := $(shell echo $(image_names_cs) | tr A-Z a-z)
|
||||
dist_images := $(addprefix $(DIST_DIR)/,$(image_names))
|
||||
|
||||
extra_files := $(wildcard extra/*)
|
||||
dist_extra_files := $(extra_files:%=dist/%)
|
||||
|
||||
define copy_image_rule =
|
||||
$(DIST_DIR)/$(1): $(shell find $(IMAGE_DIR) -iname $(1))
|
||||
@mkdir -p $(DIST_DIR)
|
||||
cp --preserve $$< $$@
|
||||
endef
|
||||
|
||||
build_metas := $(dist_images:$(DIST_DIR)/%.jpg=$(BUILD_DIR)/%.meta.json)
|
||||
dist_html := $(dist_images:$(DIST_DIR)/%.jpg=$(DIST_DIR)/%.html)
|
||||
|
||||
### RULES ###
|
||||
|
||||
all: $(dist_html) $(dist_extra_files)
|
||||
|
||||
clean:
|
||||
rm -rf $(DIST_DIR) $(BUILD_DIR)
|
||||
|
||||
$(foreach f,$(image_names),$(eval $(call copy_image_rule,$(f))))
|
||||
|
||||
$(BUILD_DIR)/%.meta.json: $(DIST_DIR)/%.jpg extract-metadata.py
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
./extract-metadata.py --root $(DIST_DIR) $< > $@
|
||||
|
||||
$(DIST_DIR)/%.html: $(BUILD_DIR)/%.meta.json photo.hbs
|
||||
@mkdir -p $(DIST_DIR)
|
||||
handlebars-cli photo.hbs "$$(cat $<)" > $@
|
||||
|
||||
$(DIST_DIR)/extra/%: extra/%
|
||||
@mkdir -p $(DIST_DIR)/extra
|
||||
cp -r $< $@
|
38
extra/photo.css
Normal file
38
extra/photo.css
Normal file
@ -0,0 +1,38 @@
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
}
|
||||
|
||||
.my-epic-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 20em;
|
||||
grid-template-rows: 1fr;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.picture-frame {
|
||||
padding: 1em;
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.picture-frame img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.text-frame {
|
||||
padding: 1em;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
font-size: 1.5em;
|
||||
margin-top: 0;
|
||||
}
|
77
extract-metadata.py
Executable file
77
extract-metadata.py
Executable file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import clize
|
||||
import exif
|
||||
import json
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def exif_content_to_json(tag, content):
|
||||
match content:
|
||||
case Enum() as e:
|
||||
return e.name
|
||||
case int() as i:
|
||||
return i
|
||||
case float() as f:
|
||||
return f
|
||||
case str() as s:
|
||||
if tag.startswith("datetime"):
|
||||
dt = datetime.strptime(s, "%Y:%m:%d %H:%M:%S")
|
||||
return dt.strftime("%Y-%m-%d %H:%M")
|
||||
return s
|
||||
case _:
|
||||
return {"pyType": str(type(content)), "stringified": str(content)}
|
||||
|
||||
|
||||
def extract_exif_data(image_path: Path) -> dict[str, Any]:
|
||||
with image_path.open("rb") as image_file:
|
||||
image = exif.Image(image_file)
|
||||
|
||||
exif_data = {}
|
||||
for tag in image.list_all():
|
||||
try:
|
||||
content = image[tag]
|
||||
except:
|
||||
# Some keys are in list_all() but can't be accessed for my camera :(
|
||||
# Skip them.
|
||||
continue
|
||||
exif_data[tag] = exif_content_to_json(tag, content)
|
||||
|
||||
return exif_data
|
||||
|
||||
|
||||
def extract_stat_data(image_path: Path):
|
||||
FIELDS = ["st_size"]
|
||||
|
||||
s = image_path.stat()
|
||||
stat_data = {}
|
||||
for field in FIELDS:
|
||||
stat_data[field] = getattr(s, field)
|
||||
|
||||
return stat_data
|
||||
|
||||
|
||||
|
||||
def extract_metadata(image_path: Path, *, root: Path):
|
||||
exif_data = extract_exif_data(image_path)
|
||||
stat_data = extract_stat_data(image_path)
|
||||
|
||||
metadata = {
|
||||
"exif": exif_data,
|
||||
"stat": stat_data,
|
||||
"path": {
|
||||
"name": image_path.name,
|
||||
"relativeToRoot": str(image_path.relative_to(root)),
|
||||
},
|
||||
}
|
||||
|
||||
print(json.dumps(metadata, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
clize.run(extract_metadata)
|
@ -23,6 +23,7 @@
|
||||
packages.handlebars-rust
|
||||
pkgs.imagemagick
|
||||
pkgs.gnumake
|
||||
(pkgs.python3.withPackages (ps: [ps.clize ps.exif]))
|
||||
];
|
||||
};
|
||||
}
|
||||
|
24
photo.hbs
Normal file
24
photo.hbs
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ path.name }}</title>
|
||||
<link rel="stylesheet" href="./extra/photo.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="my-epic-layout">
|
||||
<div class="picture-frame">
|
||||
<img src="{{ path.relativeToRoot }}" alt="{{ path.name }}">
|
||||
</div>
|
||||
<div class="text-frame">
|
||||
<h1>{{ path.name }}</h2>
|
||||
<p>{{ exif.datetime }}</p>
|
||||
<p>
|
||||
Exposure Time <strong>{{ exif.exposure_time }}s</strong><br>
|
||||
Aperture <strong>f{{ exif.aperture_value }}</strong><br>
|
||||
ISO <strong>{{ exif.photographic_sensitivity }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user