reorder
This commit is contained in:
@@ -1300,6 +1300,17 @@ def _write_all_static() -> None:
|
||||
if is_archived:
|
||||
archived_count += 1
|
||||
|
||||
tags_val = p[16]
|
||||
tags_list = []
|
||||
if tags_val:
|
||||
if isinstance(tags_val, str):
|
||||
try:
|
||||
tags_list = json.loads(tags_val)
|
||||
except Exception:
|
||||
tags_list = []
|
||||
elif isinstance(tags_val, list):
|
||||
tags_list = tags_val
|
||||
|
||||
db_images.append({
|
||||
"filename": p[0],
|
||||
"name": p[1],
|
||||
@@ -1317,6 +1328,7 @@ def _write_all_static() -> None:
|
||||
"faceswap_source_video": p[13],
|
||||
"archived": is_archived,
|
||||
"is_source": bool(p[15]) if p[15] else False,
|
||||
"tags": tags_list,
|
||||
})
|
||||
print(f"[static] write_all: {len(db_images)} total images, {archived_count} archived")
|
||||
try:
|
||||
@@ -2132,6 +2144,11 @@ def generate_video(req: VideoStitchRequest):
|
||||
if r.returncode != 0:
|
||||
raise HTTPException(500, f"ffmpeg error: {r.stderr.decode(errors='replace')[:600]}")
|
||||
|
||||
try:
|
||||
_make_video_poster(out_path)
|
||||
except Exception as pe:
|
||||
print(f"[generate-video] poster extraction error: {pe}")
|
||||
|
||||
# Register in DB so it shows up in the gallery
|
||||
person = database.get_person(req.filenames[0])
|
||||
group_id = person[1] if person and person[1] else naming.get_base_name(req.filenames[0])
|
||||
@@ -2774,6 +2791,317 @@ def extract_face_endpoint(filename: str):
|
||||
return {"status": "queued", "filename": filename}
|
||||
|
||||
|
||||
class ImageTagsRequest(BaseModel):
|
||||
tags: list[str]
|
||||
|
||||
class TagActionRequest(BaseModel):
|
||||
action: str # "add" or "remove" or "toggle"
|
||||
tag: str
|
||||
|
||||
class BulkMoveRequest(BaseModel):
|
||||
filenames: list[str]
|
||||
target_strip: str
|
||||
|
||||
def estimate_nsfw_21plus(filename: str) -> bool:
|
||||
"""Run WD tagger and return True if image is 21+ (NSFW/sensitive/explicit/nudity)."""
|
||||
try:
|
||||
output_dir = _load_output_dir()
|
||||
fpath = os.path.join(output_dir, filename)
|
||||
if not os.path.exists(fpath):
|
||||
return False
|
||||
|
||||
pil_img = Image.open(fpath)
|
||||
# Run tagger with 0.2 threshold to capture sensitive/explicit content
|
||||
tags = _run_tagger(pil_img, threshold=0.2)
|
||||
|
||||
for t in tags:
|
||||
tag_name = t["tag"].lower().replace("_", " ")
|
||||
# Check rating category
|
||||
if t["cat"] == 9 and tag_name in ("explicit", "questionable", "sensitive"):
|
||||
if t["score"] >= 0.25:
|
||||
return True
|
||||
# Check other explicit keywords in name
|
||||
if any(nsfw_word in tag_name for nsfw_word in ("nude", "naked", "breasts", "pussy", "nipples", "ass", "panties", "underwear", "lingerie", "sex", "erotic")):
|
||||
if t["score"] >= 0.3:
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"[nsfw-estimator] Error estimating for {filename}: {e}")
|
||||
return False
|
||||
|
||||
@app.post("/images/{filename:path}/tags")
|
||||
def set_image_tags(filename: str, req: ImageTagsRequest):
|
||||
person = database.get_person(filename)
|
||||
if not person:
|
||||
raise HTTPException(404, "Image not found")
|
||||
|
||||
tags_list = list(set([str(t).upper().strip() for t in req.tags if t]))
|
||||
|
||||
archived = "ARCHIVED" in tags_list
|
||||
hidden = "HIDDEN" in tags_list
|
||||
is_source = "SOURCE" in tags_list
|
||||
|
||||
if archived:
|
||||
if "VISIBLE" in tags_list:
|
||||
tags_list.remove("VISIBLE")
|
||||
else:
|
||||
if not hidden:
|
||||
if "VISIBLE" not in tags_list:
|
||||
tags_list.append("VISIBLE")
|
||||
|
||||
if hidden:
|
||||
if "VISIBLE" in tags_list:
|
||||
tags_list.remove("VISIBLE")
|
||||
else:
|
||||
if not archived:
|
||||
if "VISIBLE" not in tags_list:
|
||||
tags_list.append("VISIBLE")
|
||||
|
||||
conn = database.get_db_connection()
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("""
|
||||
UPDATE person
|
||||
SET tags = %s,
|
||||
archived = %s,
|
||||
hidden = %s,
|
||||
is_source = %s
|
||||
WHERE filename = %s
|
||||
""", (json.dumps(tags_list), archived, hidden, is_source, filename))
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.close()
|
||||
database._put_db_connection(conn)
|
||||
|
||||
_invalidate_static()
|
||||
return {"filename": filename, "tags": tags_list}
|
||||
|
||||
@app.post("/images/{filename:path}/tag-action")
|
||||
def tag_action_endpoint(filename: str, req: TagActionRequest):
|
||||
person = database.get_person(filename)
|
||||
if not person:
|
||||
raise HTTPException(404, "not found")
|
||||
|
||||
tags_val = person[2]
|
||||
tags_list = []
|
||||
if tags_val:
|
||||
if isinstance(tags_val, str):
|
||||
try:
|
||||
tags_list = json.loads(tags_val)
|
||||
except Exception:
|
||||
tags_list = []
|
||||
elif isinstance(tags_val, list):
|
||||
tags_list = tags_val
|
||||
|
||||
action = req.action.lower()
|
||||
tag_name = req.tag.upper().strip()
|
||||
|
||||
if action == "add":
|
||||
if tag_name not in tags_list:
|
||||
tags_list.append(tag_name)
|
||||
elif action == "remove":
|
||||
if tag_name in tags_list:
|
||||
tags_list.remove(tag_name)
|
||||
elif action == "toggle":
|
||||
if tag_name in tags_list:
|
||||
tags_list.remove(tag_name)
|
||||
else:
|
||||
tags_list.append(tag_name)
|
||||
|
||||
if tag_name == "LIKE" and "LIKE" in tags_list:
|
||||
if "DISLIKE" in tags_list:
|
||||
tags_list.remove("DISLIKE")
|
||||
elif tag_name == "DISLIKE" and "DISLIKE" in tags_list:
|
||||
if "LIKE" in tags_list:
|
||||
tags_list.remove("LIKE")
|
||||
|
||||
archived = "ARCHIVED" in tags_list
|
||||
hidden = "HIDDEN" in tags_list
|
||||
is_source = "SOURCE" in tags_list
|
||||
|
||||
if archived:
|
||||
if "VISIBLE" in tags_list:
|
||||
tags_list.remove("VISIBLE")
|
||||
else:
|
||||
if not hidden:
|
||||
if "VISIBLE" not in tags_list:
|
||||
tags_list.append("VISIBLE")
|
||||
|
||||
if hidden:
|
||||
if "VISIBLE" in tags_list:
|
||||
tags_list.remove("VISIBLE")
|
||||
else:
|
||||
if not archived:
|
||||
if "VISIBLE" not in tags_list:
|
||||
tags_list.append("VISIBLE")
|
||||
|
||||
conn = database.get_db_connection()
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("""
|
||||
UPDATE person
|
||||
SET tags = %s,
|
||||
archived = %s,
|
||||
hidden = %s,
|
||||
is_source = %s
|
||||
WHERE filename = %s
|
||||
""", (json.dumps(tags_list), archived, hidden, is_source, filename))
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.close()
|
||||
database._put_db_connection(conn)
|
||||
|
||||
_invalidate_static()
|
||||
return {"filename": filename, "tags": tags_list}
|
||||
|
||||
@app.post("/images/{filename:path}/source")
|
||||
def set_image_source(filename: str, body: dict):
|
||||
is_source = bool(body.get("source", False))
|
||||
person = database.get_person(filename)
|
||||
if not person:
|
||||
raise HTTPException(404, "not found")
|
||||
|
||||
tags_val = person[2]
|
||||
tags_list = []
|
||||
if tags_val:
|
||||
if isinstance(tags_val, str):
|
||||
try:
|
||||
tags_list = json.loads(tags_val)
|
||||
except Exception:
|
||||
tags_list = []
|
||||
elif isinstance(tags_val, list):
|
||||
tags_list = tags_val
|
||||
|
||||
if is_source:
|
||||
if "SOURCE" not in tags_list:
|
||||
tags_list.append("SOURCE")
|
||||
else:
|
||||
if "SOURCE" in tags_list:
|
||||
tags_list.remove("SOURCE")
|
||||
|
||||
conn = database.get_db_connection()
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("UPDATE person SET is_source = %s, tags = %s WHERE filename = %s", (is_source, json.dumps(tags_list), filename))
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.close()
|
||||
database._put_db_connection(conn)
|
||||
|
||||
_invalidate_static()
|
||||
return {"filename": filename, "is_source": is_source, "tags": tags_list}
|
||||
|
||||
@app.post("/images/{filename:path}/estimate-21plus")
|
||||
def estimate_image_21plus(filename: str):
|
||||
is_21plus = estimate_nsfw_21plus(filename)
|
||||
person = database.get_person(filename)
|
||||
if not person:
|
||||
raise HTTPException(404, "not found")
|
||||
|
||||
tags_val = person[2]
|
||||
tags_list = []
|
||||
if tags_val:
|
||||
if isinstance(tags_val, str):
|
||||
try:
|
||||
tags_list = json.loads(tags_val)
|
||||
except Exception:
|
||||
tags_list = []
|
||||
elif isinstance(tags_val, list):
|
||||
tags_list = tags_val
|
||||
|
||||
if is_21plus:
|
||||
if "21+" not in tags_list:
|
||||
tags_list.append("21+")
|
||||
else:
|
||||
if "21+" in tags_list:
|
||||
tags_list.remove("21+")
|
||||
|
||||
conn = database.get_db_connection()
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("UPDATE person SET tags = %s WHERE filename = %s", (json.dumps(tags_list), filename))
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.close()
|
||||
database._put_db_connection(conn)
|
||||
|
||||
_invalidate_static()
|
||||
return {"filename": filename, "21+": is_21plus, "tags": tags_list}
|
||||
|
||||
@app.post("/images/bulk-move")
|
||||
def bulk_move_endpoint(req: BulkMoveRequest):
|
||||
if not req.filenames:
|
||||
return {"status": "ok", "moved": 0}
|
||||
|
||||
target = req.target_strip.upper().strip()
|
||||
conn = database.get_db_connection()
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
for filename in req.filenames:
|
||||
cur.execute("SELECT tags, archived, hidden, is_source FROM person WHERE filename = %s", (filename,))
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
continue
|
||||
tags_val, archived, hidden, is_source = row
|
||||
tags_list = []
|
||||
if tags_val:
|
||||
if isinstance(tags_val, str):
|
||||
try:
|
||||
tags_list = json.loads(tags_val)
|
||||
except Exception:
|
||||
tags_list = []
|
||||
elif isinstance(tags_val, list):
|
||||
tags_list = tags_val
|
||||
|
||||
if target == "VISIBLE":
|
||||
archived = False
|
||||
hidden = False
|
||||
if "VISIBLE" not in tags_list:
|
||||
tags_list.append("VISIBLE")
|
||||
if "ARCHIVED" in tags_list:
|
||||
tags_list.remove("ARCHIVED")
|
||||
if "HIDDEN" in tags_list:
|
||||
tags_list.remove("HIDDEN")
|
||||
elif target == "HIDDEN":
|
||||
archived = False
|
||||
hidden = True
|
||||
if "HIDDEN" not in tags_list:
|
||||
tags_list.append("HIDDEN")
|
||||
if "VISIBLE" in tags_list:
|
||||
tags_list.remove("VISIBLE")
|
||||
if "ARCHIVED" in tags_list:
|
||||
tags_list.remove("ARCHIVED")
|
||||
elif target == "ARCHIVED":
|
||||
archived = True
|
||||
hidden = False
|
||||
if "ARCHIVED" not in tags_list:
|
||||
tags_list.append("ARCHIVED")
|
||||
if "VISIBLE" in tags_list:
|
||||
tags_list.remove("VISIBLE")
|
||||
if "HIDDEN" in tags_list:
|
||||
tags_list.remove("HIDDEN")
|
||||
elif target == "SOURCE":
|
||||
is_source = True
|
||||
if "SOURCE" not in tags_list:
|
||||
tags_list.append("SOURCE")
|
||||
|
||||
cur.execute("""
|
||||
UPDATE person
|
||||
SET tags = %s,
|
||||
archived = %s,
|
||||
hidden = %s,
|
||||
is_source = %s
|
||||
WHERE filename = %s
|
||||
""", (json.dumps(tags_list), archived, hidden, is_source, filename))
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.close()
|
||||
database._put_db_connection(conn)
|
||||
|
||||
_invalidate_static()
|
||||
return {"status": "ok", "moved": len(req.filenames)}
|
||||
|
||||
|
||||
class FaceSimilarRequest(BaseModel):
|
||||
group_id: str
|
||||
limit: int = 12
|
||||
|
||||
Reference in New Issue
Block a user