OverviewFilesCommitsIssuesDocsReleasesPeers

folio / rename.py

193 lines · python ref:
#!/usr/bin/env python3
"""Rename images or collections and keep everything in sync.

Usage:
    python3 rename.py image <collection> <old-name> <new-name>
    python3 rename.py collection <old-name> <new-name>
    python3 rename.py list [collection]

Examples:
    python3 rename.py list
    python3 rename.py list in-dreams
    python3 rename.py image in-dreams "1009494620_364f90e07e_o" "tangled-roots"
    python3 rename.py collection in-dreams "dreams"

Names are without extensions -- the script handles .jpg and .md automatically.
"""

import json
import os
import sys

BASE = os.path.dirname(os.path.abspath(__file__))
IMAGES_DIR = os.path.join(BASE, "images")
CONFIG_PATH = os.path.join(BASE, "site.json")


def load_config():
    if os.path.exists(CONFIG_PATH):
        with open(CONFIG_PATH, "r") as f:
            return json.load(f)
    return {}


def save_config(config):
    with open(CONFIG_PATH, "w") as f:
        json.dump(config, f, indent=2)
        f.write("\n")


def list_collections():
    ignore = {"old", "_archive", "thumbs"}
    # Also read ignore list from site.json
    config = load_config()
    collections_config = config.get("collections", {})
    ignore.update(collections_config.get("ignore", []))

    collections = []
    if not os.path.isdir(IMAGES_DIR):
        return collections
    for name in sorted(os.listdir(IMAGES_DIR)):
        path = os.path.join(IMAGES_DIR, name)
        if os.path.isdir(path) and name not in ignore:
            collections.append(name)
    return collections


def list_images(collection):
    coll_dir = os.path.join(IMAGES_DIR, collection)
    if not os.path.isdir(coll_dir):
        print(f"Error: collection '{collection}' not found", file=sys.stderr)
        sys.exit(1)

    images = []
    for name in sorted(os.listdir(coll_dir)):
        if name == "thumbs" or name.endswith(".md"):
            continue
        filepath = os.path.join(coll_dir, name)
        if os.path.isfile(filepath):
            images.append(name)
    return images


def rename_image(collection, old_name, new_name):
    coll_dir = os.path.join(IMAGES_DIR, collection)
    thumbs_dir = os.path.join(coll_dir, "thumbs")

    if not os.path.isdir(coll_dir):
        print(f"Error: collection '{collection}' not found", file=sys.stderr)
        sys.exit(1)

    old_jpg = old_name + ".jpg"
    new_jpg = new_name + ".jpg"
    old_md = old_name + ".md"
    new_md = new_name + ".md"

    moved = []

    # Full-res
    src = os.path.join(coll_dir, old_jpg)
    dst = os.path.join(coll_dir, new_jpg)
    if os.path.exists(src):
        os.rename(src, dst)
        moved.append(f"  {old_jpg} -> {new_jpg}")
    else:
        print(f"Warning: {src} not found", file=sys.stderr)

    # Thumbnail
    src = os.path.join(thumbs_dir, old_jpg)
    dst = os.path.join(thumbs_dir, new_jpg)
    if os.path.exists(src):
        os.rename(src, dst)
        moved.append(f"  thumbs/{old_jpg} -> thumbs/{new_jpg}")

    # Markdown sidecar
    src = os.path.join(coll_dir, old_md)
    dst = os.path.join(coll_dir, new_md)
    if os.path.exists(src):
        os.rename(src, dst)
        moved.append(f"  {old_md} -> {new_md}")

    # Update site.json collections order if present
    config = load_config()
    collections_config = config.get("collections", {})
    order = collections_config.get("order", [])
    # Collection-level order does not track individual images,
    # so no update needed here unless a per-image order is added later.

    if moved:
        print(f"Renamed '{old_name}' -> '{new_name}' in {collection}/:")
        for m in moved:
            print(m)
    else:
        print("Nothing was renamed.")


def rename_collection(old_name, new_name):
    old_dir = os.path.join(IMAGES_DIR, old_name)
    new_dir = os.path.join(IMAGES_DIR, new_name)

    if not os.path.isdir(old_dir):
        print(f"Error: collection '{old_name}' not found", file=sys.stderr)
        sys.exit(1)

    if os.path.exists(new_dir):
        print(f"Error: '{new_name}' already exists", file=sys.stderr)
        sys.exit(1)

    os.rename(old_dir, new_dir)
    print(f"Renamed directory: {old_name}/ -> {new_name}/")

    # Update site.json collections order
    config = load_config()
    collections_config = config.get("collections", {})
    order = collections_config.get("order", [])
    if old_name in order:
        order = [new_name if n == old_name else n for n in order]
        collections_config["order"] = order
        config["collections"] = collections_config
        save_config(config)
        print(f"Updated site.json: '{old_name}' -> '{new_name}'")


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == "list":
        if len(sys.argv) >= 3:
            collection = sys.argv[2]
            images = list_images(collection)
            print(f"{collection}/ ({len(images)} images):")
            for img in images:
                print(f"  {img}")
        else:
            collections = list_collections()
            for coll in collections:
                images = list_images(coll)
                print(f"{coll}/ ({len(images)} images)")

    elif cmd == "image":
        if len(sys.argv) != 5:
            print("Usage: rename.py image <collection> <old-name> <new-name>")
            sys.exit(1)
        rename_image(sys.argv[2], sys.argv[3], sys.argv[4])

    elif cmd == "collection":
        if len(sys.argv) != 4:
            print("Usage: rename.py collection <old-name> <new-name>")
            sys.exit(1)
        rename_collection(sys.argv[2], sys.argv[3])

    else:
        print(f"Unknown command: {cmd}")
        print(__doc__)
        sys.exit(1)


if __name__ == "__main__":
    main()