diff --git a/config.py b/config.py
index 64d0bfc..3411e6b 100644
--- a/config.py
+++ b/config.py
@@ -8,31 +8,22 @@ import os
# Base directory of the repo (this file lives in the project root)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-# Paths to the folders that contain source images. Can be overridden at runtime
-# via the IMAGE_DIRS environment variable (colon-separated paths).
-_IMAGE_DIRS_DEFAULT = [
- "/media/Portrait",
- "/media/Landscape",
+# Paths to the folders that contain source images. Add as many as you like.
+IMAGE_DIRS = [
+ "/mnt/secret-items/sd-outputs/Sorted/Images/Portrait",
+ "/mnt/secret-items/sd-outputs/Sorted/Images/Landscape",
]
-_IMAGE_DIRS_ENV = os.getenv("IMAGE_DIRS")
-if _IMAGE_DIRS_ENV:
- IMAGE_DIRS = _IMAGE_DIRS_ENV.split(":")
-else:
- IMAGE_DIRS = _IMAGE_DIRS_DEFAULT
-
# Backwards-compatibility: first directory
-IMAGE_DIR = IMAGE_DIRS[0] if IMAGE_DIRS else ""
+IMAGE_DIR = IMAGE_DIRS[0]
+
from typing import Optional
-# Data directory (override with DATA_DIR env var)
-DATA_DIR = os.getenv("DATA_DIR", BASE_DIR)
-
# SQLite database file that stores selections & metadata
-DB_PATH = os.path.join(DATA_DIR, "image_selections.db")
+DB_PATH = os.path.join(BASE_DIR, "data/image_selections.db")
-# Default port for the HTTP server (override with PORT env var)
-PORT = int(os.getenv("PORT", 8888))
+# Default port for the HTTP server
+PORT = 8000
# ---------------------------------------------------------------------------
# NSFW detection configuration
diff --git a/config_custom.py b/config_custom.py
new file mode 100644
index 0000000..64d0bfc
--- /dev/null
+++ b/config_custom.py
@@ -0,0 +1,83 @@
+import os
+
+# Configuration constants for the SWIPER application
+# --------------------------------------------------
+# Centralising these values avoids circular imports
+# and makes it easy to update paths / ports later
+
+# Base directory of the repo (this file lives in the project root)
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+
+# Paths to the folders that contain source images. Can be overridden at runtime
+# via the IMAGE_DIRS environment variable (colon-separated paths).
+_IMAGE_DIRS_DEFAULT = [
+ "/media/Portrait",
+ "/media/Landscape",
+]
+
+_IMAGE_DIRS_ENV = os.getenv("IMAGE_DIRS")
+if _IMAGE_DIRS_ENV:
+ IMAGE_DIRS = _IMAGE_DIRS_ENV.split(":")
+else:
+ IMAGE_DIRS = _IMAGE_DIRS_DEFAULT
+
+# Backwards-compatibility: first directory
+IMAGE_DIR = IMAGE_DIRS[0] if IMAGE_DIRS else ""
+from typing import Optional
+
+# Data directory (override with DATA_DIR env var)
+DATA_DIR = os.getenv("DATA_DIR", BASE_DIR)
+
+# SQLite database file that stores selections & metadata
+DB_PATH = os.path.join(DATA_DIR, "image_selections.db")
+
+# Default port for the HTTP server (override with PORT env var)
+PORT = int(os.getenv("PORT", 8888))
+
+# ---------------------------------------------------------------------------
+# NSFW detection configuration
+# ---------------------------------------------------------------------------
+# List of keywords that, if present in an image's prompt data, should mark the
+# image as NSFW. Feel free to customise this list as appropriate for your own
+# needs.
+NSFW_KEYWORDS = [
+ "nude",
+ "nudity",
+ "porn",
+ "explicit",
+ "sexual",
+ "sex",
+ "boobs",
+ "nipples",
+ "penis",
+ "vagina",
+ "pussy",
+ "cum",
+ "fellatio",
+ "blowjob",
+ "cunnilingus",
+ "paizuri",
+ "rape",
+ "handjob",
+ "lingerie",
+ "bikini",
+ "latex",
+ "saliva",
+ "ass",
+ "condom",
+ ]
+
+# ---------------------------------------------------------------------------
+# Utility helpers
+# ---------------------------------------------------------------------------
+
+def find_image_file(rel_path: str) -> Optional[str]:
+ """Return absolute path to `rel_path` by searching all IMAGE_DIRS.
+
+ Returns None if file is not found in any configured directory.
+ """
+ for base in IMAGE_DIRS:
+ abs_path = os.path.join(base, rel_path)
+ if os.path.exists(abs_path):
+ return abs_path
+ return None
diff --git a/docker-compose.yml b/docker-compose.yml
index 4da4440..c71e79b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -30,4 +30,4 @@ services:
- ./data:/data
# Uncomment the line below if you maintain a custom config.py alongside
# the compose file and want to override the image copy at runtime.
- - ./config.py:/app/config.py:ro
+ - ./config_custom.py:/app/config.py:ro
diff --git a/handler.py b/handler.py
index 1557afa..bd1dbda 100644
--- a/handler.py
+++ b/handler.py
@@ -65,6 +65,8 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
self.serve_random_image()
elif path == "/selections":
self.serve_selections()
+ elif path == "/image-count":
+ self.serve_image_count()
elif path.startswith("/images/"):
self.serve_image(path[8:])
elif path == "/favicon.ico":
@@ -249,6 +251,68 @@ class ImageSwipeHandler(BaseHTTPRequestHandler):
data = {"selections": get_selections()}
self._json_response(data)
+ def serve_image_count(self) -> None:
+ """Return the total count of images available for the current filter."""
+ parsed = urllib.parse.urlparse(self.path)
+ query_params = urllib.parse.parse_qs(parsed.query)
+ orientation_str = query_params.get("orientation", ["all"])[0]
+ orientations = [o.strip() for o in orientation_str.split(',')]
+ search_keywords_str = query_params.get("search", [""])[0].strip()
+ allow_nsfw = query_params.get("allow_nsfw", ["0"])[0] == "1"
+ search_keywords = [kw.strip() for kw in search_keywords_str.split(',') if kw.strip()]
+ actions_str = query_params.get("actions", ["Unactioned"])[0]
+ actions = [a.strip() for a in actions_str.split(',') if a.strip()]
+
+ conn = sqlite3.connect(DB_PATH)
+ cur = conn.cursor()
+ query = """
+ SELECT COUNT(*) FROM image_metadata meta
+ LEFT JOIN prompt_details pd ON meta.path = pd.image_path
+ """
+ params: List[str] = []
+ where_clauses = ["(meta.actioned IS NULL OR meta.actioned != 'purged')"]
+
+ # Action filter
+ action_conditions = []
+ action_params = []
+ if "Unactioned" in actions:
+ action_conditions.append("meta.actioned IS NULL")
+ actions.remove("Unactioned")
+ if actions:
+ placeholders = ", ".join("?" for _ in actions)
+ action_conditions.append(f"meta.actioned IN ({placeholders})")
+ action_params.extend(actions)
+
+ if action_conditions:
+ where_clauses.append(f"({' OR '.join(action_conditions)})")
+ params.extend(action_params)
+
+ # Orientation filter
+ if "all" not in orientations and orientations:
+ placeholders = ", ".join("?" for _ in orientations)
+ where_clauses.append(f"meta.orientation IN ({placeholders})")
+ params.extend(orientations)
+
+ # NSFW filter
+ if not allow_nsfw:
+ where_clauses.append("meta.nsfw = 0")
+
+ # Keyword filter
+ if search_keywords:
+ for keyword in search_keywords:
+ # Search only the positive prompt (pd.positive_prompt)
+ where_clauses.append("pd.positive_prompt LIKE ?")
+ params.append(f"%{keyword}%")
+
+ if where_clauses:
+ query += " WHERE " + " AND ".join(where_clauses)
+
+ cur.execute(query, params)
+ count = cur.fetchone()[0]
+ conn.close()
+
+ self._json_response({"count": count})
+
def serve_resolutions(self) -> None:
# Collect resolutions across all configured directories
resolutions_set = set()
diff --git a/history.html b/history.html
index 3f420a6..ab69324 100644
--- a/history.html
+++ b/history.html
@@ -165,6 +165,7 @@
-
+
+