cd /news/ai-tools/download-all-poses-at-https-old-redd… · home › topics › ai-tools › article
[ARTICLE · art-160219] src=gist.github.com ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Download all poses at https://old.reddit.com/r/unstable_diffusion/comments/1xhfrxq/how_to_use_pose_reference_images_to_create/

A developer published a standard-library Python script that scrapes every original-size PNG from the ai-pose-gallery.pages.dev pose reference site and sorts the images into folders by the site's own series and category metadata. The script downloads in parallel with six workers, resumes by skipping existing files, and emits a catalog.csv mapping file names to Japanese pose names, categories, and angles, plus optional keyword-based focus folders built as hard links.

by read7 min views2 publishedOct 28, 2026

| | #!/usr/bin/env python3 | | | """Download every original-size PNG from https://ai-pose-gallery.pages.dev/ | | | and sort them into folders using the site's own series / category data. | | | No extra packages needed (standard library only). | | | Usage: | | | python download_poses_sorted.py # download + sort + focus folders | | | python download_poses_sorted.py --no-focus # skip the _by_focus folders | | | python download_poses_sorted.py --out D:\poses | | | Re-running skips files that are already downloaded, so you can stop and | | | resume at any time. | | | Result: | | | pose_pngs/ |

|  | solo/full_body_standard/<category>/ (365 images) | 
|  | solo/full_body_sexy/<category>/ (399 images) | 
|  | solo/upper_body/<category>/ (400 images) | 
|  | duo/<category>/ (275 images) | 
|  | _by_focus/<tag>/ (keyword-based extras, see FOCUS below) | 
|  | catalog.csv (file name -> Japanese pose name, category, angle) | 

| | Each pose has 4-5 angle images (front/high/low/side/diag); they sit next to each | | | other in the same folder because they share the pose number in the file name. | | | """ | | | import argparse | | | import csv | | | import json | | | import os | | | import re | | | import shutil | | | import sys | | | import time | | | import urllib.request | | | from collections import Counter | | | from concurrent.futures import ThreadPoolExecutor, as_completed | | | from pathlib import Path |

|  | BASE = "https://ai-pose-gallery.pages.dev/" | 
|  | HEADERS = {"User-Agent": "Mozilla/5.0"} | 

| | WORKERS = 6 # parallel downloads; keep modest to be polite to the host |

|  | # Site series (系統) -> top-level folder | 
|  | SERIES = { | 

| | "全身1人・標準系": "solo/full_body_standard", | | | "全身1人・セクシー系": "solo/full_body_sexy", | | | "上半身1人": "solo/upper_body", | | | "2人絡み": "duo", | | | } |

|  | # Site category (分類) -> folder name | 
|  | CATEGORY = { | 

| | # solo, full body (standard + sexy) | | | "立ち": "standing", | | | "座り": "sitting", | | | "しゃがみ": "crouching", | | | "膝立ち": "kneeling", | | | "膝立ち・四つん這い": "kneeling_all_fours", | | | "寝そべり": "lying", | | | "前かがみ": "leaning_forward", | | | "のけぞり・反り": "arched_back", | | | "ひねり": "twisting", | | | "横座り・女の子座り": "side_sitting", | | | "特殊": "special", | | | # solo, upper body | | | "かわいい系": "cute", | | | "セクシー・露骨寄り系": "sexy_explicit", | | | "胸寄せ・谷間強調系": "chest_emphasis", | | | # duo | | | "立位": "standing", | | | "立位密着": "standing_close", | | | "座位": "seated", | | | "座位密着": "seated_close", | | | "対面座位": "seated_face_to_face", | | | "背面座位": "seated_back_facing", | | | "正常位": "missionary", | | | "後背位": "from_behind", | | | "騎乗位": "on_top", | | | "側位": "side_lying", | | | "密着": "close_embrace", | | | "顔寄せ系": "faces_close", | | | } | | | # Extra keyword-based views, built as hard links (or copies if links fail) in | | | # _by_focus/<tag>/. These match the Japanese POSE NAME, so they are rough: | | | # edit the patterns freely. Only a handful of poses mention the buttocks by name. | | | FOCUS = { | | | "butt": r"お尻|ヒップ|尻", | | | "chest": r"胸|バスト|谷間", | | | "face": r"顔", | | | "legs": r"脚|足|太もも|太腿|開脚", | | | "back": r"背中", | | | } | | | # Whole site categories that also count for a focus tag |

|  | FOCUS_CATEGORY = { | 
|  | "chest": {"胸寄せ・谷間強調系"}, | 
|  | "face": {"顔寄せ系"}, | 

| | } |

|  | def get(url): | 
|  | req = urllib.request.Request(url, headers=HEADERS) | 
|  | with urllib.request.urlopen(req, timeout=60) as r: | 
|  | return r.read() | 
|  | def safe(name): | 
|  | return re.sub(r'[\\/:*?"<>\|]+', "_", name).strip() or "other" | 
|  | def fetch_catalog(): | 
|  | html = get(BASE).decode("utf-8", "replace") | 
|  | items, seen = [], set() | 
|  | # The gallery page embeds its catalog as {"id": ..., "系統": ..., "原寸": ...} objects. | 
|  | for m in re.finditer(r'\{"id":[^{}]*\}', html): | 

| | try: | | | o = json.loads(m.group(0)) | | | except ValueError: | | | continue |

|  | if o.get("原寸") and o.get("id") not in seen: | 
|  | seen.add(o["id"]) | 
|  | items.append(o) | 

| | return items |

|  | def folder_for(o): | 
|  | series = SERIES.get(o.get("系統"), safe(o.get("系統", "other"))) | 
|  | if series == "duo": | 
|  | cat = CATEGORY.get(o.get("分類"), safe(o.get("分類", "other"))) | 
|  | return Path("duo") / cat | 
|  | cat = CATEGORY.get(o.get("分類"), safe(o.get("分類", "other"))) | 
|  | return Path(*series.split("/")) / cat | 
|  | def target_path(out, o): | 
|  | return out / folder_for(o) / Path(o["原寸"]).name | 
|  | def download(out, o): | 
|  | dest = target_path(out, o) | 
|  | if dest.exists() and dest.stat().st_size > 0: | 

| | return "skipped" | | | dest.parent.mkdir(parents=True, exist_ok=True) | | | err = None | | | for attempt in range(3): | | | try: |

|  | data = get(BASE + o["原寸"]) | 
|  | tmp = dest.with_suffix(".part") | 
|  | tmp.write_bytes(data) | 
|  | tmp.replace(dest) | 

| | return "ok" | | | except Exception as e: # network hiccup: wait and retry | | | err = e |

|  | time.sleep(1 + attempt) | 
|  | return f"failed: {err}" | 
|  | def write_catalog(out, items): | 
|  | with open(out / "catalog.csv", "w", newline="", encoding="utf-8-sig") as f: | 
|  | w = csv.writer(f) | 

| | w.writerow(["file", "folder", "series_ja", "category_ja", "pose_number", "pose_name_ja", "angle"]) | | | for o in items: |

|  | name = Path(o["原寸"]).name | 
|  | angle = Path(o["原寸"]).stem.rsplit("_", 1)[-1] | 
|  | w.writerow([name, folder_for(o).as_posix(), o.get("系統", ""), o.get("分類", ""), | 
|  | o.get("ポーズ番号", ""), o.get("ポーズ名", ""), angle]) | 
|  | def build_focus(out, items): | 
|  | counts = Counter() | 

| | for o in items: |

|  | src = target_path(out, o) | 
|  | if not src.exists(): | 

| | continue |

|  | for tag, pattern in FOCUS.items(): | 
|  | if re.search(pattern, o.get("ポーズ名", "")) or o.get("分類") in FOCUS_CATEGORY.get(tag, ()): | 

| | dst_dir = out / "_by_focus" / tag | | | dst_dir.mkdir(parents=True, exist_ok=True) | | | dst = dst_dir / src.name | | | if dst.exists(): | | | continue | | | try: | | | os.link(src, dst) # no extra disk space | | | except OSError: |

|  | shutil.copy2(src, dst) | 
|  | counts[tag] += 1 | 

| | return counts | | | def main(): | | | ap = argparse.ArgumentParser(description=doc, formatter_class=argparse.RawDescriptionHelpFormatter) |

|  | ap.add_argument("--out", default=str(Path(__file__).resolve().parent / "pose_pngs"), | 
|  | help="output folder (default: pose_pngs next to this script)") | 
|  | ap.add_argument("--no-focus", action="store_true", help="do not build the _by_focus folders") | 
|  | args = ap.parse_args() | 
|  | out = Path(args.out) | 
|  | items = fetch_catalog() | 

| | if not items: | | | sys.exit("Could not read the image catalog from the page (site layout may have changed).") |

|  | out.mkdir(parents=True, exist_ok=True) | 
|  | print(f"Found {len(items)} images. Saving to {out}") | 
|  | done = failed = 0 | 
|  | with ThreadPoolExecutor(max_workers=WORKERS) as pool: | 
|  | futures = {pool.submit(download, out, o): o for o in items} | 
|  | for i, fut in enumerate(as_completed(futures), 1): | 
|  | result = fut.result() | 
|  | if result.startswith("failed"): | 

| | failed += 1 | | | print(f" {futures[fut]['原寸']}: {result}") | | | else: | | | done += 1 |

|  | if i % 50 == 0 or i == len(items): | 
|  | print(f" {i}/{len(items)} processed") | 
|  | write_catalog(out, items) | 

| | if not args.no_focus: | | | counts = build_focus(out, items) | | | if counts: |

|  | print("Focus folders: " + ", ".join(f"{k}={v}" for k, v in sorted(counts.items()))) | 
|  | print(f"Finished: {done} saved/skipped, {failed} failed.") | 

| | if failed: | | | print("Run the script again to retry the failed ones.") |

|  | if __name__ == "__main__": | 
|  | main() |
── more in #ai-tools 4 stories · sorted by recency
── more on @ai-pose-gallery.pages.dev 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/download-all-poses-a…] indexed:0 read:7min 2026-10-28 · —