From 029201da1e485171747771a24255bc5e3830ea6c Mon Sep 17 00:00:00 2001
From: Luca Beltrame <lbeltrame@kde.org>
Date: Sun, 5 Jun 2022 21:42:21 +0200
Subject: [PATCH] A crappy wallpaper sorter by aspect ratio

---
 misc/wallpaper_sorter.py | 133 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)
 create mode 100755 misc/wallpaper_sorter.py

diff --git a/misc/wallpaper_sorter.py b/misc/wallpaper_sorter.py
new file mode 100755
index 0000000..3a4ae6a
--- /dev/null
+++ b/misc/wallpaper_sorter.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python3
+
+import argparse
+from typing import List
+from pathlib import Path
+
+from PIL import Image
+
+
+SOURCES = ["konachan", "wallhaven", "yandere", "moe_imouto"]
+RATIO_PATHS = ("4_3", "16_9", "16_10", "vertical")
+
+
+def get_wallpaper_source(filename: Path) -> str:
+
+    if filename.name.startswith("Konachan.com"):
+        return "konachan"
+    elif filename.name.startswith("wallhaven"):
+        return "wallhaven"
+    elif filename.name.startswith("yande.re"):
+        return "yandere"
+    elif filename.name.startswith("moe"):
+        return "moe_imouto"
+    else:
+        return ""
+
+
+def ratio_and_resolution(filename: Path, guess_source=True) -> Path:
+
+    img = Image.open(filename)
+    resolution = f"{img.width}x{img.height}"
+    ratio = img.width / img.height
+
+    check_ratios = [("4:3", 4/3), ("16:9", 16/9), ("16:10", 16/10)]
+    # Calculate the distance from each ratio of the image in question
+    distances = {label: (ratio-ref)**2 for label, ref in check_ratios}
+    # Calculate the ratio with the minimum distance
+    min_dist = min(distances, key=distances.get).replace(":", "_")
+
+    if guess_source:
+        source = get_wallpaper_source(filename)
+    else:
+        source = ""
+
+    new_filename = "_".join((filename.stem, resolution)) + filename.suffix
+
+    # Special case phone wallpapers for now
+    if img.height > img.width:
+        min_dist = "vertical"
+
+    if source:
+        final_name = Path(min_dist) / source / new_filename
+    else:
+        final_name = Path(min_dist) / new_filename
+
+    return final_name
+
+
+def organize_images(files: List[Path],
+                    destination_path: Path, dry_run=True) -> None:
+
+    for ratio_set in RATIO_PATHS:
+        if not dry_run:
+            (destination_path / ratio_set).mkdir(exist_ok=True)
+            for source in SOURCES:
+                (destination_path / ratio_set / source).mkdir(exist_ok=True)
+
+    for element in files:
+        new_name = destination_path / ratio_and_resolution(element)
+
+        if dry_run:
+            print(f"{element} -> {new_name}")
+            continue
+
+        if not new_name.exists():
+            element.rename(new_name)
+            # new_name.symlink_to(element)
+
+
+def get_image_list(source: Path) -> List[Path]:
+
+    candidates = list()
+    for element in source.glob("**/*"):
+
+        if element.is_dir():
+            continue
+
+        # Exclude already processed directories
+        if any(part in RATIO_PATHS for part in element.parts):
+            continue
+
+        if element.suffix in (".png", ".jpg", ".webp", ".jpeg", ".bmp"):
+            candidates.append(element)
+
+    return candidates
+
+
+def cleanup(destination: Path) -> None:
+
+    for element in RATIO_PATHS:
+        full_path = destination / element
+
+        for source in SOURCES:
+            source_path = full_path / source
+
+            if not any(source_path.iterdir()):
+                source_path.rmdir()
+
+        if not any(full_path.iterdir()):
+            full_path.rmdir()
+
+
+def main():
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--dry-run", action="store_true",
+                        help="Only simulate")
+    parser.add_argument("source", help="Source wallpaper directory")
+    parser.add_argument("destination", help="Destination path to move data to")
+
+    options = parser.parse_args()
+    source = Path(options.source).absolute()
+
+    destination = Path(options.destination).absolute()
+
+    images = get_image_list(source)
+    organize_images(images, destination, options.dry_run)
+
+    cleanup(destination)
+
+
+if __name__ == "__main__":
+    main()