{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreid2wkdhzveiipkxfc7xaawjrcarulkq2u2wx6a6pl7n2clhihundq",
    "uri": "at://did:plc:yxqneqalyamqefe3iezpocu7/app.bsky.feed.post/3mjzkapx35ek2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreifxy2pkhfziiazibhgvts3cr35ufgzfusjldegwjhdnglgxcsexte"
    },
    "mimeType": "image/png",
    "size": 47560
  },
  "description": "I have a folder named to_digikam on my Fedora computer where photos and videos land. They come from many places: Syncthing transfers from my Mac, camera offloads, or cloud exports. They arrive with generic names like IMG_0842.heicor PXL_20260421.jpg, sitting in one flat, chaotic pile until I find the time to deal with them.\n\nI wanted a workflow that would handle one tedious part of my organization workflow automatically. Specifically, a script that would:\n\n 1. Extract the YYYY-MM-DD from the act",
  "path": "/a-python-script-that-sorts-my-photos-while-im-doing-something-else/",
  "publishedAt": "2026-04-21T17:50:28.000Z",
  "site": "https://billmoriarty.com",
  "textContent": "I have a folder named `to_digikam` on my Fedora computer where photos and videos land. They come from many places: Syncthing transfers from my Mac, camera offloads, or cloud exports. They arrive with generic names like `IMG_0842.heic`or `PXL_20260421.jpg`, sitting in one flat, chaotic pile until I find the time to deal with them.\n\nI wanted a workflow that would handle one tedious part of my organization workflow automatically. Specifically, a script that would:\n\n  1. **Extract the YYYY-MM-DD** from the actual photo metadata.\n  2. **Rename the file** by prepending that date to the original filename.\n  3. **Move the file** into a `year/unsorted` directory within my main digiKam library.\n\n\n\nI’m a big fan of Derek Sivers’ approach to photo file naming: embedding context directly into the filename. Something like `2025-09-12 Sky at the beach.jpg` is more useful to me than `IMG_5502.jpg`.\n\nThe filesystem's \"modified date\" is fragile - many normal copy and sync operations reset it. The only real record is the metadata that tracks when the photo was taken. We can use **ExifTool** (the `perl-Image-ExifTool` package on Fedora), to read that internal timestamp and place it in the filename. The code then moves the file to `Pictures/photos/[YEAR]/Unsorted/`. I still keep the \"Unsorted\" folder because then I can look at it in digiKam and decide if it should be in an album or have a tag or something.\n\nPython script to sort images\n\n**The Mac-to-Fedora Handshake:**\nA cool part of this setup is the visual feedback. On my Mac, I export a photo into a synced folder. A few minutes later, the file vanishes. Because Syncthing is keeping the folders mirrored, the Fedora machine moving the file out of `to_digikam` triggers a \"delete\" on the Mac. That empty folder means Fedora handled it.\n\nFor the schedule, I went with a systemd user timer instead of a traditional cron job. On Fedora, this is the native way to handle background tasks. Unlike cron, systemd timers:\n\n  * are aware of system states (if the machine is asleep, the timer can catch up immediately upon wake).\n  * log directly to `journalctl` for easy troubleshooting.\n  * run entirely under my user account, keeping the automation isolated from the root system.\n\n\n\nI described the logic to Gemini and it generated the Python backbone. After a few manual tweaks for my specific folder structures...success!\n\n\n    import os\n    import shutil\n    import subprocess\n    from pathlib import Path\n\n    # --- CONFIGURATION ---\n    # Edit these two lines for your machine.\n    SOURCE_DIR = Path(\"/home/YOUR_USERNAME/path/to/intake_folder\")\n    DEST_BASE = Path(\"/home/YOUR_USERNAME/Pictures/photos\")\n    # ---------------------\n\n    def get_date_taken(file_path):\n        \"\"\"Uses exiftool to get the DateTimeOriginal metadata.\"\"\"\n        try:\n            cmd = [\"exiftool\", \"-DateTimeOriginal\", \"-d\", \"%Y-%m-%d %Y\", \"-S\", \"-s\", str(file_path)]\n            result = subprocess.run(cmd, capture_output=True, text=True, check=True)\n            if result.stdout:\n                return result.stdout.strip().split(' ')\n        except Exception as e:\n            print(f\"Error reading metadata for {file_path}: {e}\")\n        return None, None\n\n    def organize_photos():\n        if not SOURCE_DIR.exists():\n            print(f\"Source directory {SOURCE_DIR} does not exist.\")\n            return\n        for file_path in SOURCE_DIR.iterdir():\n            if file_path.is_dir() or file_path.name.startswith('.'):\n                continue\n            date_str, year_str = get_date_taken(file_path)\n            if date_str and year_str:\n                new_name = f\"{date_str} {file_path.name}\"\n                target_dir = DEST_BASE / year_str / \"Unsorted\"\n                target_path = target_dir / new_name\n                target_dir.mkdir(parents=True, exist_ok=True)\n                print(f\"Moving: {file_path.name} -> {target_path}\")\n                shutil.move(str(file_path), str(target_path))\n            else:\n                print(f\"Skipping {file_path.name}: No EXIF date found.\")\n\n    if __name__ == \"__main__\":\n        organize_photos()\n\nI expect to edit this for edge cases and more date related naming in case that one metadata field is not always accurate, but this is a great start.",
  "title": "A Python script that sorts my photos while I'm doing something else",
  "updatedAt": "2026-04-24T19:44:10.884Z"
}