name: upload-photos description: Rename, convert, and upload new gallery photos to Cloudflare R2, then update photos.yaml. Use when the user has new photos to add to the gallery. argument-hint: "[source-directory]"
Process and upload new gallery photos to Cloudflare R2.
Input: Optionally specify the source directory containing new photos. Defaults to photo_temp/.
Naming Convention
All photos must follow this format:
YYYYMMDD-{CAMERA_ID}.JPG
- Date prefix:
YYYYMMDD— the date the photo was taken - Separator:
-(hyphen only, never_) - Camera ID: the original camera filename (e.g.
DSC01753,R0000754) - Extension:
.JPG(uppercase)
Steps
1. Inventory source files
List all files in the source directory. Classify each file:
| Category | Detection | Action |
|---|---|---|
| Already correct | Matches YYYYMMDD-*.JPG |
No change needed |
| Lowercase extension | Matches YYYYMMDD-*.jpg |
mv to .JPG |
| Missing date prefix | No YYYYMMDD- prefix |
Extract date from EXIF via mdls -name kMDItemContentCreationDate <file>, then rename |
| Underscore separator | Contains _ between date and ID |
Replace _ with - |
| PNG format | .png or .PNG extension |
Convert to JPEG |
Report the inventory to the user and ask for confirmation before proceeding. If any files are missing a date prefix and EXIF data is unavailable, ask the user to provide the date.
2. Rename and convert
Process files in this order:
- Convert PNG to JPEG: Use macOS
sips:sips -s format jpeg "<input>.png" --out "<output>.JPG" - Rename
.jpgto.JPG: Usemv - Fix separators: Replace
_with- - Add date prefixes: Prepend
YYYYMMDD- - Delete original
.pngfiles after successful conversion
After processing, list all files and confirm they match YYYYMMDD-{CAMERA_ID}.JPG.
3. Get dimensions (with EXIF orientation check)
IMPORTANT: sips -g pixelWidth/pixelHeight returns raw pixel dimensions and ignores EXIF orientation. Photos taken in portrait mode may report landscape dimensions (e.g. 6192x4128 instead of 4128x6192). Use mdls to get the display-correct dimensions:
mdls -name kMDItemPixelWidth -name kMDItemPixelHeight <file>
mdls applies the EXIF orientation tag, so kMDItemPixelWidth and kMDItemPixelHeight reflect the actual display orientation. Always use mdls values for width and height in photos.yaml.
After obtaining all dimensions, run a cross-check on every photo to catch orientation mismatches:
for f in *.JPG; do
sw=$(sips -g pixelWidth "$f" 2>/dev/null | awk '/pixelWidth/{print $2}')
sh=$(sips -g pixelHeight "$f" 2>/dev/null | awk '/pixelHeight/{print $2}')
mw=$(mdls -name kMDItemPixelWidth "$f" | awk '{print $3}')
mh=$(mdls -name kMDItemPixelHeight "$f" | awk '{print $3}')
if [ "$sw" != "$mw" ] || [ "$sh" != "$mh" ]; then
echo "MISMATCH: $f sips=${sw}x${sh} mdls=${mw}x${mh}"
fi
done
Any file that shows MISMATCH has an EXIF orientation rotation — use the mdls values (not sips) for that file's width and height in photos.yaml.
4. Upload to Cloudflare R2
Upload each file using wrangler with the --remote flag (critical — without it, files only go to local storage):
npx wrangler r2 object put "xuno8-photos/<filename>" --file="<source>/<filename>" --remote
After uploading, verify at least 2-3 files via the public URL:
curl -sI "https://images.xuno8.com/<filename>" | head -3
Expect HTTP/2 200 with content-type: image/jpeg.
5. Update src/data/photos.yaml
Append new entries to src/data/photos.yaml. Each entry has this format:
- src: 'YYYYMMDD-CAMERA_ID.JPG'
alt: 'YYYYMMDD-CAMERA_ID'
width: <pixelWidth>
height: <pixelHeight>
src: the exact filename uploaded to R2alt: same as filename without extensionwidth/height: pixel dimensions from step 3- Do NOT modify existing entries
6. Verify
- Run
npm run buildto confirm the site builds successfully - Report a summary: how many photos were added, any issues encountered