Files
theread.me/scripts/organize-photo-highlights.py
T

209 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""One-time migration of September 2026 uploads into individual highlighted photos."""
from datetime import datetime, timedelta, timezone
from pathlib import Path
import html
import json
import re
ART = Path("_art")
UPLOAD = "/img/arts/uploads"
OLD = [
"260cc99527c13f256d4ea39d0959f5daa942c888ea882852e73fd94f6c30b7db",
"474ab3283ce8801ac6e59b67b65b4169167053873a5a8db4a15b840a3448266c",
"7c832afa1b73023d8f2391ff40c221be9ffd468a1a96a6bf4900cbf95595d31d",
"b18a2f1c7127077c801160782012cb164759f0b9dd41762205b45e4c9a07d673",
"c53f20236f166632bff163339c8e789cc09b0d7e63c5b531b1fc20705460975b",
]
armenia = [
(OLD[2], 9), (OLD[2], 8), (OLD[2], 7), (OLD[2], 6),
(OLD[2], 5), (OLD[2], 4), (OLD[1], 0),
]
romania = [
(OLD[2], 0), (OLD[2], 1), (OLD[4], 0), (OLD[4], 1),
(OLD[4], 2), (OLD[4], 3), (OLD[4], 4), (OLD[0], 0), (OLD[3], 0),
]
ungrouped = [(OLD[2], 2), (OLD[2], 3)]
ordered = romania + armenia + ungrouped
assert len(ordered) == len(set(ordered)) == 18
collection_groups = [
(
"iran-analog-2024",
datetime(2024, 10, 25, 23, 59, 59, tzinfo=timezone.utc),
"Iran - Hamadan, Kermanshah, Tabriz",
[
"Taq-e-Bostan_000521270020.jpg", "Taq-e-Bostan_000521270019.jpg",
"Taq-e-Bostan_000521270018.jpg", "Tabriz_000521270013.jpg",
"Tabriz_000521270012.jpg", "Iran_000521270023.jpg",
"Iran_000521270021.jpg", "Hamadan_000521270035.jpg",
"Hamadan_000521270034.jpg", "Hamadan_000521270033.jpg",
"Hamadan_000521270032.jpg", "Hamadan_000521270031.jpg",
"Ganjname,-Hamadan_000521270029.jpg", "Cowboy-from-Hamadan_000521270030.jpg",
"Bistoon_000521270027.jpg", "Bistoon_000521270026.jpg",
"Bistoon_000521270025.jpg", "Bazaar-e-Tabriz_000521270010.jpg",
"Bazaar-e-Tabriz_000521270009.jpg", "Bazaar-e-Tabriz_000521270008.jpg",
("dresden-prague-iran-2024", "Babataher.jpg"),
],
),
(
"kazakhstan-2024",
datetime(2024, 10, 9, 23, 59, 59, tzinfo=timezone.utc),
"Kazakhstan - World Nomad Games",
[
"x_DSC-0999.jpg", "x_DSC-0994.jpg",
"World-Nomad-Games,-Kazakhstan_DSC-0685.jpg",
"World-Nomad-Games,-Kazakhstan_DSC-0536.jpg",
"World-Nomad-Games,-Kazakhstan_DSC-0472.jpg",
"World-Nomad-Games,-Kazakhstan_DSC-0298.jpg",
"World-Nomad-Games,-Kazakhstan_DSC-0251.jpg",
"Kazakhstan_DSC-0870.jpg", "Kazakhstan_DSC-0865.jpg",
],
),
(
"dresden-prague-iran-2024",
datetime(2024, 10, 9, 23, 50, 59, tzinfo=timezone.utc),
"Dresden & Berlin",
[
"x_000512370028.jpg", "x_000512370027.jpg", "x_000512370026.jpg",
"x_000512370024.jpg", "x_000512370022.jpg", "x_000512370013.jpg",
"x_000512350002.jpg", "x_000512350001.jpg",
"Let-me-tell-you-a-story_000512370015.jpg", "Dresden_000512370034.jpg",
"Dresden_000512370033.jpg", "Dresden_000512370032.jpg",
"Dresden_000512370019.jpg", "Dresden_000512370018.jpg",
"Dresden_000512370017.jpg", "Dresden_000512370016.jpg",
],
),
(
"dresden-prague-iran-2024",
datetime(2024, 10, 9, 23, 40, 59, tzinfo=timezone.utc),
"Iran - Tehran & Qom",
[
"Home_000512360003.jpg", "Somewhere-in-Tehran_2.jpg",
"Somewhere-in-Tehran.jpg", "Grandmother's-old-room.jpg",
],
),
(
"dresden-prague-iran-2024",
datetime(2024, 10, 9, 23, 30, 59, tzinfo=timezone.utc),
"Prague",
[
"x_000512370006.jpg", "x_000512370005.jpg", "x_000512370003.jpg",
"Prague_000512370012.jpg", "Prague_000512360031.jpg",
"Prague_000512360030.jpg", "Prague_000512360029.jpg",
"Prague_000512360027.jpg", "Prague_000512360021.jpg",
"Prague_000512360020.jpg", "Prague_000512360019.jpg",
],
),
(
"dresden-prague-iran-2024",
datetime(2024, 10, 9, 23, 20, 59, tzinfo=timezone.utc),
"Belfast & Tallaght",
[
"x_000512350014.jpg", "x_000512350012.jpg", "x_000512350011.jpg",
"x_000512350006.jpg", "x_000512350004.jpg", "x_000512350003.jpg",
"Bobby-Sands.jpg", "Belfast_000512350032.jpg",
"Belfast_000512350027.jpg", "Belfast_000512350026.jpg",
"Belfast_000512350022.jpg", "Belfast_000512350018.jpg",
],
),
]
paris = "85a558a3fcf582e6f0637d6927bb5a074de422f30a389ab9589630b9c163fc0e"
paris_lille = "e013523e4dda324d603c89f93ef3e83abbcd82cb11a16f85e53b670e8613d34f"
geneva = "18a628ae4ea72d0f9a68de6acfbc698449fe10e49ecf990a8ddced0b2e32949d"
iranian_memorial = "bf23ab31ef7b33150e9e99cb31f14b1bc27bc8143293a32bb1d4ac2193a493c6"
foto_64 = "1bb19ce5b6a5570ddda1754bb77583dbfb2b4c534ee88e85a69f511ab47b1a14"
uploaded_highlight_groups = [
(
"Iranian Martyrs’ Memorial Cemetery",
datetime(2025, 9, 19, 21, 43, 42, tzinfo=timezone.utc),
[(iranian_memorial, index) for index in range(2)],
),
(
"Geneva",
datetime(2025, 9, 19, 21, 42, 30, tzinfo=timezone.utc),
[(geneva, index) for index in range(3)],
),
(
"Paris & Lille",
datetime(2025, 9, 19, 21, 41, 30, tzinfo=timezone.utc),
[(paris_lille, index) for index in range(13)] +
[(paris, index) for index in range(10)],
),
(
"Foto 64",
datetime(2026, 9, 19, 12, 20, 30, tzinfo=timezone.utc),
[(foto_64, index) for index in range(4)],
),
]
anchors = {armenia[0]: "Armenia", romania[0]: "Romania"}
captions = {(OLD[2], 7): "Transmission of Flesh by Erfan Ashourioun"}
first_for_job = {}
for photo in ordered:
first_for_job.setdefault(photo[0], photo)
for job in OLD:
batch = next(ART.glob(f"*-photo-{job}.md"), None)
if batch:
batch.unlink()
start = datetime(2026, 9, 19, 12, 40, 33, tzinfo=timezone.utc)
for position, (job, index) in enumerate(ordered):
title = f"photo-{job}-{index:02}"
if (job, index) == first_for_job[job]:
title = f"photo-{job}"
date = start - timedelta(seconds=position)
lines = ["---", "layout: post", f'title: "{title}"',
f'date: "{date.isoformat()}"', "categories: art"]
if (job, index) in anchors:
lines.append(f'anchor: "{anchors[(job, index)]}"')
lines.extend(["---", ""])
if (job, index) == first_for_job[job] and (job, index) not in anchors:
lines.extend([f'<div id="photo-{job}"></div>', ""])
caption = captions.get((job, index), "")
lines.append(f'<p><img src="{UPLOAD}/{job}/{index:02}.jpg" '
f'alt="{html.escape(caption, quote=True)}" loading="lazy"></p>')
if caption:
lines.append(f'<span class="image-details">{html.escape(caption)}</span>')
target = ART / f"2026-09-19-photo-{job}-{index:02}.md"
target.write_text("\n".join(lines) + "\n")
for folder, group_start, anchor, filenames in collection_groups:
for position, member in enumerate(filenames):
member_folder, filename = member if isinstance(member, tuple) else (folder, member)
image = f"/img/arts/{member_folder}/{filename}"
matches = [path for path in ART.glob("*.md") if image in path.read_text()]
assert len(matches) == 1, (image, matches)
path = matches[0]
content = path.read_text()
date = group_start - timedelta(seconds=position)
content = re.sub(r"^date:.*$", f'date: "{date.isoformat()}"', content,
count=1, flags=re.MULTILINE)
content = re.sub(r"^anchor:.*\n", "", content, flags=re.MULTILINE)
if position == 0:
content = content.replace("categories: art\n", "categories: art\nanchor: " +
json.dumps(anchor, ensure_ascii=False) + "\n", 1)
path.write_text(content)
for anchor, group_start, photos in uploaded_highlight_groups:
for position, (job, index) in enumerate(photos):
path = ART / f"2026-09-19-photo-{job}-{index:02}.md"
content = path.read_text()
date = group_start - timedelta(seconds=position)
content = re.sub(r"^date:.*$", f'date: "{date.isoformat()}"', content,
count=1, flags=re.MULTILINE)
content = re.sub(r"^anchor:.*\n", "", content, flags=re.MULTILINE)
if position == 0:
content = content.replace("categories: art\n", "categories: art\nanchor: " +
json.dumps(anchor, ensure_ascii=False) + "\n", 1)
elif index == 0 and f'id="photo-{job}"' not in content:
# Removing this batch's old highlight must not remove its published
# receipt target from the rendered gallery.
content = content.replace("---\n\n", f'---\n\n<div id="photo-{job}"></div>\n\n', 1)
path.write_text(content)
print("Organized all configured photo highlights into contiguous groups")