From 4f388901f36f37dcb8d1cb01cbe80fec48bb5353 Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 27 Jun 2026 01:22:26 +0200 Subject: [PATCH] reorder --- backlog.md | 4 +- tour-comfy/car.html | 24 +++--- tour-comfy/config.json | 3 +- tour-comfy/edit_api.py | 151 +++++++++++++++++++++++++++++------- tour-comfy/orbit_qwen.py | 16 ++-- tour-comfy/test_orbit_12.py | 4 +- 6 files changed, 150 insertions(+), 52 deletions(-) diff --git a/backlog.md b/backlog.md index 66068e2..6aef15d 100644 --- a/backlog.md +++ b/backlog.md @@ -48,7 +48,7 @@ Scenery: - after pose generation, sometimes small black area's occur in the subject. ( presumably the _apply_transparency_black_bg addition) - studio view - orbit tab - view sometimes seems accelerated, liek the tiems is set twice. And most of all a step in the turn seems in in the wrong direction. maybe its an image-ordering bug, but i think its because the image-model generated the step 240deg into the wrong direction - Improve the UI/UX for the end-user experience. Where effort is low and impact is high. -- Use own insight for further refinement of these improvements. + - animate limbs along keyframe curves (true motion) - 3D-aware rotation / "circling around" with depth estimation - camera-orbit batch with consistent lighting across views @@ -76,7 +76,7 @@ focus op head + sam2.hyria seg + mesh (3D / mesh — pose editor is currently sam2 saifrail, connected pixels from mid? als invertor? poses assignment bij de camera positie generaties create disk 128gb 70b dolphin uncencored op server. - +- Use own insight for further refinement of these improvements. fix queen.mp moet gegenereerd worden door de htmlbuilder, niet via de webserver images.json diff --git a/tour-comfy/car.html b/tour-comfy/car.html index bf9a483..e47c08a 100644 --- a/tour-comfy/car.html +++ b/tour-comfy/car.html @@ -1867,9 +1867,9 @@ + 🗑️ Trash -
@@ -5226,7 +5226,6 @@ const poseEntries = [...selectedPoses].map(p => ({ name: p, text: availablePoses[p]?.text ?? availablePoses[p] })); if (!prompt && poseEntries.length === 0) { showToast('Enter a prompt or select a pose', 'info'); return; } - if (selectedFiles.size === 0) { showToast('No images selected', 'info'); return; } // Build parallel prompt+pose arrays const prompts = []; @@ -5642,16 +5641,6 @@ } catch(e) { console.error(e); } } - async function cleanupDb() { - try { - const r = await fetch(`${API}/db/cleanup`, { method: 'POST' }); - const d = await r.json(); - showToast(`Cleaned ${d.removed} orphan record${d.removed !== 1 ? 's' : ''}`); - if (d.removed > 0) refreshNow(); - } catch(e) { - showToast('Cleanup failed'); - } - } async function lbUndress() { const fname = lbNames[lbIdx]; @@ -6218,7 +6207,16 @@ async function submitSbGenerate() { if (!_fsModelFilename) return; const promptVal = (document.getElementById('sbGenPromptInput')?.value || '').trim(); - // Read pad values (persisted in module vars for re-renders) + const hasPoses = _fsSelectedPoses.size > 0 || _sbSelectedAngles.size > 0; + const getV = (id) => document.getElementById(id)?.value || '0'; + const padValStr = getV('sbPadTop') + getV('sbPadRight') + getV('sbPadBottom') + getV('sbPadLeft'); + const hasPadding = padValStr.replace(/[0%\s]/g, '').length > 0; + const outpaint = document.getElementById('sbPadOutpaint')?.checked; + const hasPaddingAction = hasPadding && outpaint; + + if (!hasPoses && !hasPaddingAction) { + // If no poses or padding, we'll run base_prompts + } _sbPadTop = document.getElementById('sbPadTop')?.value || '0'; _sbPadRight = document.getElementById('sbPadRight')?.value || '0'; _sbPadBottom = document.getElementById('sbPadBottom')?.value || '0'; diff --git a/tour-comfy/config.json b/tour-comfy/config.json index 67de0df..c7b1de4 100644 --- a/tour-comfy/config.json +++ b/tour-comfy/config.json @@ -1,7 +1,8 @@ { "api_url": "http://127.0.0.1:8500/edit", - "prompt": "masterpiece. high quality. hyper realistic. detailed, detailed skin", + "prompt": null, "base_prompts": [ + "masterpiece. high quality. hyper realistic. detailed, detailed skin", "detailed female nude. realistic, detailed teenage female nude. realistic", "masterpiece. high quality. realistic. detailed. female nude. photo-realistic", "masterpiece. high quality. realistic. detailed. teenage female nude. photo-realistic", diff --git a/tour-comfy/edit_api.py b/tour-comfy/edit_api.py index 19f7cf3..9455034 100644 --- a/tour-comfy/edit_api.py +++ b/tour-comfy/edit_api.py @@ -120,33 +120,40 @@ async def _track_activity(request, call_next): _last_request_time = time.time() return await call_next(request) -def _sync_car_html(): - src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car.html") - if not os.path.exists(src): - return - try: - dest = os.path.join(_load_output_dir(), "car.html") - shutil.copy2(src, dest) - print(f"[car.html] synced → {dest}") - except Exception as e: - print(f"[car.html] sync warning: {e}") +def _sync_frontend(): + for name in ["car.html", "trash.html"]: + src = os.path.join(_HERE, name) + if not os.path.exists(src): + continue + try: + dest = os.path.join(_load_output_dir(), name) + shutil.copy2(src, dest) + print(f"[{name}] synced → {dest}") + except Exception as e: + print(f"[{name}] sync warning: {e}") - -def _watch_car_html(): - src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car.html") - last_mtime = os.path.getmtime(src) if os.path.exists(src) else 0 +def _watch_frontend(): + files = ["car.html", "trash.html"] + last_mtimes = {} + for name in files: + src = os.path.join(_HERE, name) + if os.path.exists(src): + last_mtimes[name] = os.path.getmtime(src) + while True: time.sleep(1) - try: - mtime = os.path.getmtime(src) - if mtime != last_mtime: - last_mtime = mtime - dest = os.path.join(_load_output_dir(), "car.html") - shutil.copy2(src, dest) - print(f"[car.html] change detected → synced to {dest}") - except Exception: - pass - + for name in files: + src = os.path.join(_HERE, name) + if not os.path.exists(src): continue + try: + mtime = os.path.getmtime(src) + if mtime != last_mtimes.get(name): + last_mtimes[name] = mtime + dest = os.path.join(_load_output_dir(), name) + shutil.copy2(src, dest) + print(f"[{name}] change detected → synced to {dest}") + except Exception: + pass def _load_wireframe_dir() -> str: with open(CONFIG_PATH, "r") as f: @@ -160,6 +167,60 @@ def _load_faceswap_model_path() -> str: return os.path.expanduser(conf.get("faceswap_model", "~/.insightface/models/inswapper_128.onnx")) +def _run_consistency_check(): + """Identifies DB records with missing files and files on disk with no DB record.""" + try: + output_dir = _load_output_dir() + data_dir = os.path.join(output_dir, "_data") + os.makedirs(data_dir, exist_ok=True) + + persons = database.list_persons(include_archived=True) + missing_files = [] + for p in persons: + filename = p[0] + fpath = os.path.join(output_dir, filename) + if not os.path.exists(fpath): + missing_files.append({ + "filename": filename, + "group_id": p[2], + "name": p[1] + }) + + # Untracked files + db_filenames = set(p[0] for p in persons) + untracked_files = [] + if os.path.isdir(output_dir): + for f in os.listdir(output_dir): + if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp', '.mp4')): + # Skip internal folders and data files + if f.startswith('_') or f == 'car.html' or f == 'trash.html': + continue + if f not in db_filenames: + untracked_files.append(f) + + report = { + "timestamp": time.time(), + "missing_files": missing_files, + "untracked_files": untracked_files + } + _write_json(os.path.join(data_dir, "inconsistencies.json"), report) + print(f"[consistency] check complete: {len(missing_files)} missing, {len(untracked_files)} untracked") + return report + except Exception as e: + print(f"[consistency] check failed: {e}") + return None + + +def _consistency_check_daemon(): + """Runs daily consistency check.""" + # Wait for startup + time.sleep(30) + while True: + _run_consistency_check() + # Sleep for 24 hours + time.sleep(86400) + + def _idle_turntable_daemon(): """ Background daemon: when the API has been idle > IDLE_THRESHOLD seconds, @@ -308,9 +369,10 @@ def on_startup(): database.migrate_schema() except Exception as e: print(f"DB migration warning: {e}") - _sync_car_html() - threading.Thread(target=_watch_car_html, daemon=True).start() + _sync_frontend() + threading.Thread(target=_watch_frontend, daemon=True).start() threading.Thread(target=_idle_turntable_daemon, daemon=True).start() + threading.Thread(target=_consistency_check_daemon, daemon=True).start() # Mount wireframe static dir for browser video preview try: wf_dir = _load_wireframe_dir() @@ -1558,6 +1620,10 @@ def update_config(update: ConfigUpdate): return {"prompt": conf["prompt"], "seed": conf["seed"]} +class RepairRequest(BaseModel): + action: str # "delete_record", "import_file" + filename: str + class BatchRequest(BaseModel): filenames: list[str] prompt: str | list[str] @@ -2232,6 +2298,39 @@ def get_similar(filename: str, limit: int = 10): return {"filename": filename, "similar": similar} +@app.get("/db/inconsistencies") +def get_inconsistencies(run_now: bool = False): + """Return the last consistency report, optionally running a new check.""" + if run_now: + return _run_consistency_check() + output_dir = _load_output_dir() + path = os.path.join(output_dir, "_data", "inconsistencies.json") + if os.path.exists(path): + with open(path, "r") as f: + return json.load(f) + return _run_consistency_check() + +@app.post("/db/repair") +def db_repair(req: RepairRequest): + """Repair a specific inconsistency.""" + output_dir = _load_output_dir() + if req.action == "delete_record": + database.delete_person(req.filename) + _invalidate_static() + _run_consistency_check() + return {"status": "deleted", "filename": req.filename} + elif req.action == "import_file": + fpath = os.path.join(output_dir, req.filename) + if not os.path.exists(fpath): + raise HTTPException(404, "File not found on disk") + # Basic import: just register it in DB + database.upsert_person(req.filename, filepath=fpath, group_id=naming.get_base_name(req.filename)) + _invalidate_static() + _run_consistency_check() + return {"status": "imported", "filename": req.filename} + else: + raise HTTPException(400, "Invalid action") + @app.post("/db/cleanup") def db_cleanup(): """Delete DB records for files that no longer exist on disk.""" diff --git a/tour-comfy/orbit_qwen.py b/tour-comfy/orbit_qwen.py index d25dc53..b42aee4 100644 --- a/tour-comfy/orbit_qwen.py +++ b/tour-comfy/orbit_qwen.py @@ -223,16 +223,16 @@ def _angle_phrase(deg: float) -> str: "THIS IS A BACK VIEW — her back faces the camera. " "Nearly a full back view with a very subtle lean. " "Her spine is slightly to the RIGHT of center in the image. " - # "Both shoulder blades are visible, with her left shoulder blade slightly more prominent. " - "Both shoulder blades are visible. " + "Both shoulder blades are visible, with her left shoulder blade slightly more prominent. " + #"Both shoulder blades are visible. " "Face completely hidden" ) elif d < 172.5: # 165° — almost full back (right side) return ( "THIS IS A BACK VIEW — almost exactly a full back view, the tiniest lean from the right. " "Her spine is just barely to the RIGHT of center in the image. " - "Both shoulder blades, buttocks and backs of both legs are visible. " - # "Her left shoulder blade is just barely more prominent. Face completely hidden" + #"Both shoulder blades, buttocks and backs of both legs are visible. " + "Her left shoulder blade is just barely more prominent. Face completely hidden" "Face completely hidden" ) @@ -258,8 +258,8 @@ def _angle_phrase(deg: float) -> str: "THIS IS A BACK VIEW — her back faces the camera. " "Nearly a full back view with a very subtle lean from the left side. " "Her spine is slightly to the LEFT of center in the image. " - # "Both shoulder blades are visible, with her right shoulder blade slightly more prominent. " - "Both shoulder blades are visible. " + "Both shoulder blades are visible, with her right shoulder blade slightly more prominent. " + #"Both shoulder blades are visible. " "Face completely hidden" ) elif d < 232.5: # 225° — rear-left, heading toward full back @@ -274,8 +274,8 @@ def _angle_phrase(deg: float) -> str: elif d < 247.5: # 240° — three-quarter rear-left return ( "THIS IS A BACK VIEW — her back faces the camera. " - # "Three-quarter rear-left: her right shoulder blade and right hip (on the right half of the image) are most prominent. " - "Three-quarter rear-left: her right hip (on the right half of the image) are most prominent. " + "Three-quarter rear-left: her right shoulder blade and right hip (on the right half of the image) are most prominent. " + # "Three-quarter rear-left: her right hip (on the right half of the image) are most prominent. " "Her spine is on the LEFT half of the image. " "In the output image her right shoulder blade appears on the RIGHT half of the image, " "with her back turning towards the camera. " diff --git a/tour-comfy/test_orbit_12.py b/tour-comfy/test_orbit_12.py index e9f725e..7c8c36a 100644 --- a/tour-comfy/test_orbit_12.py +++ b/tour-comfy/test_orbit_12.py @@ -16,8 +16,8 @@ from orbit_qwen import run_qwen_orbit def main(): # "/mnt/zim/tour-comfy/output/20260625_045029_pose_3_20260618_173728_image.png" - input_image = "/mnt/zim/tour-comfy/output/20260618_181656_4_20260618_181600_image.png" - output_dir = "/mnt/zim/tour-comfy/output/orbit_360_test_12" + input_image = "/mnt/zim/tour-comfy/output/20260625_045029_pose_3_20260618_173728_image.png" + output_dir = "/home/mike/dev/qwen-image-edit-rapid-aio-nsfw-v23/test/orbit_360_test_13" if not os.path.exists(input_image): print(f"Error: input image not found: {input_image}")