209 lines
8.8 KiB
Python
209 lines
8.8 KiB
Python
"""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")
|