This commit is contained in:
mike
2026-06-27 01:22:26 +02:00
parent 36a244cab4
commit 4f388901f3
6 changed files with 150 additions and 52 deletions

View File

@@ -48,7 +48,7 @@ Scenery:
- after pose generation, sometimes small black area's occur in the subject. ( presumably the _apply_transparency_black_bg addition) - 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 - 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. - 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) - animate limbs along keyframe curves (true motion)
- 3D-aware rotation / "circling around" with depth estimation - 3D-aware rotation / "circling around" with depth estimation
- camera-orbit batch with consistent lighting across views - 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? sam2 saifrail, connected pixels from mid? als invertor?
poses assignment bij de camera positie generaties poses assignment bij de camera positie generaties
create disk 128gb 70b dolphin uncencored op server. create disk 128gb 70b dolphin uncencored op server.
- Use own insight for further refinement of these improvements.
fix queen.mp fix queen.mp
moet gegenereerd worden door de htmlbuilder, niet via de webserver moet gegenereerd worden door de htmlbuilder, niet via de webserver
images.json images.json

View File

@@ -1867,9 +1867,9 @@
<button class="btn" onclick="setIntervalTime(120)">2m</button> <button class="btn" onclick="setIntervalTime(120)">2m</button>
<button class="btn primary" onclick="refreshNow()">Refresh Now</button> <button class="btn primary" onclick="refreshNow()">Refresh Now</button>
<button class="btn" id="archiveViewBtn" onclick="toggleArchiveView()" title="View archived items">📦 Archive</button> <button class="btn" id="archiveViewBtn" onclick="toggleArchiveView()" title="View archived items">📦 Archive</button>
<a href="trash.html" class="btn" title="View database inconsistencies" style="text-decoration:none;display:inline-flex;align-items:center;">🗑️ Trash</a>
<button class="btn" id="selectBtn" onclick="toggleSelectMode()">Select</button> <button class="btn" id="selectBtn" onclick="toggleSelectMode()">Select</button>
<button class="btn" id="privacyBtn" onclick="togglePrivacyMode()" title="Privacy mode — hides content on focus loss (P)">🔓</button> <button class="btn" id="privacyBtn" onclick="togglePrivacyMode()" title="Privacy mode — hides content on focus loss (P)">🔓</button>
<button class="btn" onclick="cleanupDb()" title="Remove DB records for deleted/missing files" style="font-size:11px;opacity:0.6">Clean DB</button>
</div> </div>
<div class="status"> <div class="status">
<span class="status-dot" id="statusDot"></span> <span class="status-dot" id="statusDot"></span>
@@ -5226,7 +5226,6 @@
const poseEntries = [...selectedPoses].map(p => ({ name: p, text: availablePoses[p]?.text ?? availablePoses[p] })); 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 (!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 // Build parallel prompt+pose arrays
const prompts = []; const prompts = [];
@@ -5642,16 +5641,6 @@
} catch(e) { console.error(e); } } 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() { async function lbUndress() {
const fname = lbNames[lbIdx]; const fname = lbNames[lbIdx];
@@ -6218,7 +6207,16 @@
async function submitSbGenerate() { async function submitSbGenerate() {
if (!_fsModelFilename) return; if (!_fsModelFilename) return;
const promptVal = (document.getElementById('sbGenPromptInput')?.value || '').trim(); 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'; _sbPadTop = document.getElementById('sbPadTop')?.value || '0';
_sbPadRight = document.getElementById('sbPadRight')?.value || '0'; _sbPadRight = document.getElementById('sbPadRight')?.value || '0';
_sbPadBottom = document.getElementById('sbPadBottom')?.value || '0'; _sbPadBottom = document.getElementById('sbPadBottom')?.value || '0';

View File

@@ -1,7 +1,8 @@
{ {
"api_url": "http://127.0.0.1:8500/edit", "api_url": "http://127.0.0.1:8500/edit",
"prompt": "masterpiece. high quality. hyper realistic. detailed, detailed skin", "prompt": null,
"base_prompts": [ "base_prompts": [
"masterpiece. high quality. hyper realistic. detailed, detailed skin",
"detailed female nude. realistic, detailed teenage female nude. realistic", "detailed female nude. realistic, detailed teenage female nude. realistic",
"masterpiece. high quality. realistic. detailed. female nude. photo-realistic", "masterpiece. high quality. realistic. detailed. female nude. photo-realistic",
"masterpiece. high quality. realistic. detailed. teenage female nude. photo-realistic", "masterpiece. high quality. realistic. detailed. teenage female nude. photo-realistic",

View File

@@ -120,33 +120,40 @@ async def _track_activity(request, call_next):
_last_request_time = time.time() _last_request_time = time.time()
return await call_next(request) return await call_next(request)
def _sync_car_html(): def _sync_frontend():
src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car.html") for name in ["car.html", "trash.html"]:
if not os.path.exists(src): src = os.path.join(_HERE, name)
return if not os.path.exists(src):
try: continue
dest = os.path.join(_load_output_dir(), "car.html") try:
shutil.copy2(src, dest) dest = os.path.join(_load_output_dir(), name)
print(f"[car.html] synced → {dest}") shutil.copy2(src, dest)
except Exception as e: print(f"[{name}] synced → {dest}")
print(f"[car.html] sync warning: {e}") except Exception as e:
print(f"[{name}] sync warning: {e}")
def _watch_frontend():
def _watch_car_html(): files = ["car.html", "trash.html"]
src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car.html") last_mtimes = {}
last_mtime = os.path.getmtime(src) if os.path.exists(src) else 0 for name in files:
src = os.path.join(_HERE, name)
if os.path.exists(src):
last_mtimes[name] = os.path.getmtime(src)
while True: while True:
time.sleep(1) time.sleep(1)
try: for name in files:
mtime = os.path.getmtime(src) src = os.path.join(_HERE, name)
if mtime != last_mtime: if not os.path.exists(src): continue
last_mtime = mtime try:
dest = os.path.join(_load_output_dir(), "car.html") mtime = os.path.getmtime(src)
shutil.copy2(src, dest) if mtime != last_mtimes.get(name):
print(f"[car.html] change detected → synced to {dest}") last_mtimes[name] = mtime
except Exception: dest = os.path.join(_load_output_dir(), name)
pass shutil.copy2(src, dest)
print(f"[{name}] change detected → synced to {dest}")
except Exception:
pass
def _load_wireframe_dir() -> str: def _load_wireframe_dir() -> str:
with open(CONFIG_PATH, "r") as f: 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")) 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(): def _idle_turntable_daemon():
""" """
Background daemon: when the API has been idle > IDLE_THRESHOLD seconds, Background daemon: when the API has been idle > IDLE_THRESHOLD seconds,
@@ -308,9 +369,10 @@ def on_startup():
database.migrate_schema() database.migrate_schema()
except Exception as e: except Exception as e:
print(f"DB migration warning: {e}") print(f"DB migration warning: {e}")
_sync_car_html() _sync_frontend()
threading.Thread(target=_watch_car_html, daemon=True).start() threading.Thread(target=_watch_frontend, daemon=True).start()
threading.Thread(target=_idle_turntable_daemon, 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 # Mount wireframe static dir for browser video preview
try: try:
wf_dir = _load_wireframe_dir() wf_dir = _load_wireframe_dir()
@@ -1558,6 +1620,10 @@ def update_config(update: ConfigUpdate):
return {"prompt": conf["prompt"], "seed": conf["seed"]} return {"prompt": conf["prompt"], "seed": conf["seed"]}
class RepairRequest(BaseModel):
action: str # "delete_record", "import_file"
filename: str
class BatchRequest(BaseModel): class BatchRequest(BaseModel):
filenames: list[str] filenames: list[str]
prompt: str | list[str] prompt: str | list[str]
@@ -2232,6 +2298,39 @@ def get_similar(filename: str, limit: int = 10):
return {"filename": filename, "similar": similar} 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") @app.post("/db/cleanup")
def db_cleanup(): def db_cleanup():
"""Delete DB records for files that no longer exist on disk.""" """Delete DB records for files that no longer exist on disk."""

View File

@@ -223,16 +223,16 @@ def _angle_phrase(deg: float) -> str:
"THIS IS A BACK VIEW — her back faces the camera. " "THIS IS A BACK VIEW — her back faces the camera. "
"Nearly a full back view with a very subtle lean. " "Nearly a full back view with a very subtle lean. "
"Her spine is slightly to the RIGHT of center in the image. " "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, with her left shoulder blade slightly more prominent. "
"Both shoulder blades are visible. " #"Both shoulder blades are visible. "
"Face completely hidden" "Face completely hidden"
) )
elif d < 172.5: # 165° — almost full back (right side) elif d < 172.5: # 165° — almost full back (right side)
return ( return (
"THIS IS A BACK VIEW — almost exactly a full back view, the tiniest lean from the right. " "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. " "Her spine is just barely to the RIGHT of center in the image. "
"Both shoulder blades, buttocks and backs of both legs are visible. " #"Both shoulder blades, buttocks and backs of both legs are visible. "
# "Her left shoulder blade is just barely more prominent. Face completely hidden" "Her left shoulder blade is just barely more prominent. Face completely hidden"
"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. " "THIS IS A BACK VIEW — her back faces the camera. "
"Nearly a full back view with a very subtle lean from the left side. " "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. " "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, with her right shoulder blade slightly more prominent. "
"Both shoulder blades are visible. " #"Both shoulder blades are visible. "
"Face completely hidden" "Face completely hidden"
) )
elif d < 232.5: # 225° — rear-left, heading toward full back 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 elif d < 247.5: # 240° — three-quarter rear-left
return ( return (
"THIS IS A BACK VIEW — her back faces the camera. " "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 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 hip (on the right half of the image) are most prominent. "
"Her spine is on the LEFT half of the image. " "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, " "In the output image her right shoulder blade appears on the RIGHT half of the image, "
"with her back turning towards the camera. " "with her back turning towards the camera. "

View File

@@ -16,8 +16,8 @@ from orbit_qwen import run_qwen_orbit
def main(): def main():
# "/mnt/zim/tour-comfy/output/20260625_045029_pose_3_20260618_173728_image.png" # "/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" input_image = "/mnt/zim/tour-comfy/output/20260625_045029_pose_3_20260618_173728_image.png"
output_dir = "/mnt/zim/tour-comfy/output/orbit_360_test_12" output_dir = "/home/mike/dev/qwen-image-edit-rapid-aio-nsfw-v23/test/orbit_360_test_13"
if not os.path.exists(input_image): if not os.path.exists(input_image):
print(f"Error: input image not found: {input_image}") print(f"Error: input image not found: {input_image}")