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}")