Files
schmeeve-toolz/pick-most-skin
2026-05-16 19:11:25 -07:00

199 lines
7.0 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
#
# Scan ~/webGoggles (or given path) for all session screenshots.
# For each user (site/username), pick the one with the highest
# skin-to-total pixel ratio and save it as most_skin.png
# next to their bio_screenshot.png.
#
# Usage:
# ./pick-most-skin [--force] [path ...]
#
# If no path given, defaults to ~/webGoggles.
# Multiple paths can be specified.
#
# Requires: opencv-python (pip install opencv-python)
import os
import sys
import glob as _glob
import shutil
# If cv2 isn't on the normal path, check pipx-installed opencv-python
try:
import cv2
import numpy as np
except ImportError:
_pipx_venv = os.path.expanduser(
"~/.local/pipx/venvs/opencv-python/lib/python*/site-packages"
)
_matches = sorted(_glob.glob(_pipx_venv))
if _matches:
sys.path.insert(0, _matches[-1])
import cv2
import numpy as np
else:
print("Error: opencv-python not installed.")
print(" pip install opencv-python")
sys.exit(1)
# HSV ranges for skin tones (tunable)
SKIN_RANGES = [
((0, 20, 70), (20, 255, 255)), # warm/peach
((170, 20, 70), (180, 255, 255)), # reddish wrap-around
]
def skin_ratio(path: str) -> float:
"""Return fraction of pixels that look like skin (0.0 1.0)."""
img = cv2.imread(path)
if img is None:
return 0.0
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
for lower, upper in SKIN_RANGES:
mask |= cv2.inRange(hsv, np.array(lower, dtype=np.uint8),
np.array(upper, dtype=np.uint8))
return float(cv2.countNonZero(mask)) / (img.shape[0] * img.shape[1])
def find_most_skin(site_user: str) -> tuple[float, str | None]:
"""Walk sessions under site/user/, return (best_ratio, best_path)."""
sessions_dir = os.path.join(site_user, "sessions")
if not os.path.isdir(sessions_dir):
return 0.0, None
best_ratio = 0.0
best_path = None
for root, dirs, files in os.walk(sessions_dir):
if "screenshot.png" not in files:
continue
path = os.path.join(root, "screenshot.png")
ratio = skin_ratio(path)
if ratio > best_ratio:
best_ratio = ratio
best_path = path
return best_ratio, best_path
def main() -> None:
args = [a for a in sys.argv[1:] if not a.startswith("-")]
force = "--force" in sys.argv[1:]
roots = args if args else [os.path.expanduser("~/webGoggles")]
for root in roots:
root = os.path.abspath(root)
if not os.path.isdir(root):
print(f"[pick-most-skin] Skipping {root} not a directory")
continue
print(f"[pick-most-skin] Scanning {root}")
# Walk site/user directories
for site in sorted(os.listdir(root)):
site_path = os.path.join(root, site)
if not os.path.isdir(site_path):
continue
for user in sorted(os.listdir(site_path)):
user_path = os.path.join(site_path, user)
if not os.path.isdir(user_path):
continue
dest = os.path.join(user_path, "most_skin.png")
if os.path.exists(dest) and not force:
print(f" {site}/{user}: most_skin.png exists (use --force to re-scan)")
continue
ratio, best = find_most_skin(user_path)
if best is None:
continue
shutil.copy2(best, dest)
print(f" {site}/{user}: {ratio:.1%} skin -> {dest}")
# Build/refresh GALLERY symlinks
gallery = os.path.join(root, "GALLERY")
os.makedirs(gallery, exist_ok=True)
# Remove stale symlinks
for entry in os.listdir(gallery):
if entry == "index.html":
continue
entry_path = os.path.join(gallery, entry)
if os.path.islink(entry_path) or entry.endswith("_ms.png"):
os.unlink(entry_path)
entries = []
for site in sorted(os.listdir(root)):
site_path = os.path.join(root, site)
if not os.path.isdir(site_path):
continue
for user in sorted(os.listdir(site_path)):
user_path = os.path.join(site_path, user)
most_skin = os.path.join(user_path, "most_skin.png")
if not os.path.isfile(most_skin):
continue
link_name = f"{user}_ms.png"
full_link = os.path.join(gallery, link_name)
os.symlink(os.path.relpath(most_skin, gallery), full_link)
entries.append((user, link_name))
# Generate gallery HTML
html_path = os.path.join(gallery, "index.html")
count = len(entries)
rows = "".join(
f' <a href="#img-{i}" class="thumb"><img src="{fn}" loading="lazy"><span>{u}</span></a>\n'
for i, (u, fn) in enumerate(entries)
)
lbs = "".join(
f' <div id="img-{i}" class="lightbox"><a href="#"><img src="{fn}"></a></div>\n'
for i, (u, fn) in enumerate(entries)
)
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>webGoggles Gallery</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ background: #111; color: #ccc; font: 14px/1.4 system-ui, sans-serif; padding: 20px; }}
h1 {{ font-size: 1.2rem; margin-bottom: 16px; color: #888; font-weight: 400; }}
.grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }}
.thumb {{ display: block; position: relative; border-radius: 6px; overflow: hidden; background: #1a1a1a; text-decoration: none; }}
.thumb img {{ width: 100%; height: 260px; object-fit: cover; display: block; }}
.thumb span {{ display: block; padding: 6px 10px; font-size: .8rem; color: #999; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }}
.thumb:hover img {{ opacity: .85; }}
.lightbox {{ display: none; position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,.94); align-items: center; justify-content: center; }}
.lightbox:target {{ display: flex; }}
.lightbox img {{ max-width: 95vw; max-height: 95vh; object-fit: contain; border-radius: 4px; cursor: zoom-out; }}
</style>
</head>
<body>
<h1>GALLERY &middot; {count} users</h1>
<div class="grid">
{rows}</div>
{lbs}<script>
document.addEventListener('keydown', e => {{
const m = location.hash.match(/^#img-(\\d+)$/);
if (!m) return;
const i = +m[1], n = {count};
if (e.key === 'ArrowRight') location.hash = '#img-' + (i + 1 < n ? i + 1 : 0);
if (e.key === 'ArrowLeft') location.hash = '#img-' + (i > 0 ? i - 1 : n - 1);
if (e.key === 'Escape') location.hash = '#';
}});
</script></body>
</html>"""
with open(html_path, "w") as f:
f.write(html)
print(f" GALLERY/index.html — {len(entries)} entries")
print("[pick-most-skin] Done")
if __name__ == "__main__":
main()