Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee36caccd6 | ||
|
|
e226548471 | ||
|
|
b9196ef5f5 |
79
app.py
79
app.py
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
import requests
|
||||
@@ -42,6 +43,16 @@ app.config['SESSION_PERMANENT'] = False
|
||||
db.init_app(app)
|
||||
Session(app)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging
|
||||
# ---------------------------------------------------------------------------
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
)
|
||||
logger = logging.getLogger('gaze')
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generation Job Queue
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -78,6 +89,7 @@ def _enqueue_job(label, workflow, finalize_fn):
|
||||
with _job_queue_lock:
|
||||
_job_queue.append(job)
|
||||
_job_history[job['id']] = job
|
||||
logger.info("Job queued: [%s] %s", job['id'][:8], label)
|
||||
_queue_worker_event.set()
|
||||
return job
|
||||
|
||||
@@ -104,6 +116,8 @@ def _queue_worker():
|
||||
with _job_queue_lock:
|
||||
job['status'] = 'processing'
|
||||
|
||||
logger.info("Job started: [%s] %s", job['id'][:8], job['label'])
|
||||
|
||||
try:
|
||||
with app.app_context():
|
||||
# Send workflow to ComfyUI
|
||||
@@ -114,6 +128,7 @@ def _queue_worker():
|
||||
comfy_id = prompt_response['prompt_id']
|
||||
with _job_queue_lock:
|
||||
job['comfy_prompt_id'] = comfy_id
|
||||
logger.info("Job [%s] queued in ComfyUI as %s", job['id'][:8], comfy_id)
|
||||
|
||||
# Poll until done (max ~10 minutes)
|
||||
max_retries = 300
|
||||
@@ -129,15 +144,17 @@ def _queue_worker():
|
||||
if not finished:
|
||||
raise Exception("ComfyUI generation timed out")
|
||||
|
||||
logger.info("Job [%s] generation complete, finalizing...", job['id'][:8])
|
||||
# Run the finalize callback (saves image to disk / DB)
|
||||
# finalize_fn(comfy_prompt_id, job) — job is passed so callback can store result
|
||||
job['finalize_fn'](comfy_id, job)
|
||||
|
||||
with _job_queue_lock:
|
||||
job['status'] = 'done'
|
||||
logger.info("Job done: [%s] %s", job['id'][:8], job['label'])
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Queue] Job {job['id']} failed: {e}")
|
||||
logger.exception("Job failed: [%s] %s — %s", job['id'][:8], job['label'], e)
|
||||
with _job_queue_lock:
|
||||
job['status'] = 'failed'
|
||||
job['error'] = str(e)
|
||||
@@ -2078,16 +2095,8 @@ def replace_cover_from_preview(slug):
|
||||
return redirect(url_for('detail', slug=slug))
|
||||
|
||||
def _log_workflow_prompts(label, workflow):
|
||||
"""Print the final assembled ComfyUI prompts in a consistent, readable block."""
|
||||
"""Log the final assembled ComfyUI prompts in a consistent, readable block."""
|
||||
sep = "=" * 72
|
||||
print(f"\n{sep}")
|
||||
print(f" WORKFLOW PROMPTS [{label}]")
|
||||
print(sep)
|
||||
print(f" Checkpoint : {workflow['4']['inputs'].get('ckpt_name', '(not set)')}")
|
||||
print(f" Seed : {workflow['3']['inputs'].get('seed', '(not set)')}")
|
||||
print(f" Resolution : {workflow['5']['inputs'].get('width', '?')} x {workflow['5']['inputs'].get('height', '?')}")
|
||||
print(f" Sampler : {workflow['3']['inputs'].get('sampler_name', '?')} / {workflow['3']['inputs'].get('scheduler', '?')} steps={workflow['3']['inputs'].get('steps', '?')} cfg={workflow['3']['inputs'].get('cfg', '?')}")
|
||||
# LoRA chain summary
|
||||
active_loras = []
|
||||
for node_id, label_str in [("16", "char/look"), ("17", "outfit"), ("18", "action"), ("19", "style/detail/scene")]:
|
||||
if node_id in workflow:
|
||||
@@ -2095,16 +2104,26 @@ def _log_workflow_prompts(label, workflow):
|
||||
if name:
|
||||
w = workflow[node_id]["inputs"].get("strength_model", "?")
|
||||
active_loras.append(f"{label_str}:{name.split('/')[-1]}@{w:.3f}" if isinstance(w, float) else f"{label_str}:{name.split('/')[-1]}@{w}")
|
||||
print(f" LoRAs : {' | '.join(active_loras) if active_loras else '(none)'}")
|
||||
print(f" [+] Positive : {workflow['6']['inputs'].get('text', '')}")
|
||||
print(f" [-] Negative : {workflow['7']['inputs'].get('text', '')}")
|
||||
face_text = workflow.get('14', {}).get('inputs', {}).get('text', '')
|
||||
hand_text = workflow.get('15', {}).get('inputs', {}).get('text', '')
|
||||
lines = [
|
||||
sep,
|
||||
f" WORKFLOW PROMPTS [{label}]",
|
||||
sep,
|
||||
f" Checkpoint : {workflow['4']['inputs'].get('ckpt_name', '(not set)')}",
|
||||
f" Seed : {workflow['3']['inputs'].get('seed', '(not set)')}",
|
||||
f" Resolution : {workflow['5']['inputs'].get('width', '?')} x {workflow['5']['inputs'].get('height', '?')}",
|
||||
f" Sampler : {workflow['3']['inputs'].get('sampler_name', '?')} / {workflow['3']['inputs'].get('scheduler', '?')} steps={workflow['3']['inputs'].get('steps', '?')} cfg={workflow['3']['inputs'].get('cfg', '?')}",
|
||||
f" LoRAs : {' | '.join(active_loras) if active_loras else '(none)'}",
|
||||
f" [+] Positive : {workflow['6']['inputs'].get('text', '')}",
|
||||
f" [-] Negative : {workflow['7']['inputs'].get('text', '')}",
|
||||
]
|
||||
if face_text:
|
||||
print(f" [F] Face : {face_text}")
|
||||
lines.append(f" [F] Face : {face_text}")
|
||||
if hand_text:
|
||||
print(f" [H] Hand : {hand_text}")
|
||||
print(f"{sep}\n")
|
||||
lines.append(f" [H] Hand : {hand_text}")
|
||||
lines.append(sep)
|
||||
logger.info("\n%s", "\n".join(lines))
|
||||
|
||||
|
||||
def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_negative=None, outfit=None, action=None, style=None, detailer=None, scene=None, width=None, height=None, checkpoint_data=None, look=None, fixed_seed=None):
|
||||
@@ -2150,7 +2169,7 @@ def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_nega
|
||||
workflow["16"]["inputs"]["clip"] = ["4", 1] # From checkpoint
|
||||
model_source = ["16", 0]
|
||||
clip_source = ["16", 1]
|
||||
print(f"Character LoRA: {char_lora_name} @ {_w16}")
|
||||
logger.debug("Character LoRA: %s @ %s", char_lora_name, _w16)
|
||||
|
||||
# Outfit LoRA (Node 17) - chains from character LoRA or checkpoint
|
||||
outfit_lora_data = outfit.data.get('lora', {}) if outfit else {}
|
||||
@@ -2166,7 +2185,7 @@ def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_nega
|
||||
workflow["17"]["inputs"]["clip"] = clip_source
|
||||
model_source = ["17", 0]
|
||||
clip_source = ["17", 1]
|
||||
print(f"Outfit LoRA: {outfit_lora_name} @ {_w17}")
|
||||
logger.debug("Outfit LoRA: %s @ %s", outfit_lora_name, _w17)
|
||||
|
||||
# Action LoRA (Node 18) - chains from previous LoRA or checkpoint
|
||||
action_lora_data = action.data.get('lora', {}) if action else {}
|
||||
@@ -2182,7 +2201,7 @@ def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_nega
|
||||
workflow["18"]["inputs"]["clip"] = clip_source
|
||||
model_source = ["18", 0]
|
||||
clip_source = ["18", 1]
|
||||
print(f"Action LoRA: {action_lora_name} @ {_w18}")
|
||||
logger.debug("Action LoRA: %s @ %s", action_lora_name, _w18)
|
||||
|
||||
# Style/Detailer/Scene LoRA (Node 19) - chains from previous LoRA or checkpoint
|
||||
# Priority: Style > Detailer > Scene (Scene LoRAs are rare but supported)
|
||||
@@ -2200,7 +2219,7 @@ def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_nega
|
||||
workflow["19"]["inputs"]["clip"] = clip_source
|
||||
model_source = ["19", 0]
|
||||
clip_source = ["19", 1]
|
||||
print(f"Style/Detailer LoRA: {style_lora_name} @ {_w19}")
|
||||
logger.debug("Style/Detailer LoRA: %s @ %s", style_lora_name, _w19)
|
||||
|
||||
# Apply connections to all model/clip consumers
|
||||
workflow["3"]["inputs"]["model"] = model_source
|
||||
@@ -2266,7 +2285,7 @@ def _get_default_checkpoint():
|
||||
|
||||
@app.route('/get_missing_characters')
|
||||
def get_missing_characters():
|
||||
missing = Character.query.filter((Character.image_path == None) | (Character.image_path == '')).all()
|
||||
missing = Character.query.filter((Character.image_path == None) | (Character.image_path == '')).order_by(Character.filename).all()
|
||||
return {'missing': [{'slug': c.slug, 'name': c.name} for c in missing]}
|
||||
|
||||
@app.route('/clear_all_covers', methods=['POST'])
|
||||
@@ -2281,7 +2300,7 @@ def clear_all_covers():
|
||||
def generate_missing():
|
||||
missing = Character.query.filter(
|
||||
(Character.image_path == None) | (Character.image_path == '')
|
||||
).order_by(Character.name).all()
|
||||
).order_by(Character.filename).all()
|
||||
|
||||
if not missing:
|
||||
flash("No characters missing cover images.")
|
||||
@@ -2351,7 +2370,7 @@ def save_defaults(slug):
|
||||
|
||||
@app.route('/get_missing_outfits')
|
||||
def get_missing_outfits():
|
||||
missing = Outfit.query.filter((Outfit.image_path == None) | (Outfit.image_path == '')).all()
|
||||
missing = Outfit.query.filter((Outfit.image_path == None) | (Outfit.image_path == '')).order_by(Outfit.filename).all()
|
||||
return {'missing': [{'slug': o.slug, 'name': o.name} for o in missing]}
|
||||
|
||||
@app.route('/clear_all_outfit_covers', methods=['POST'])
|
||||
@@ -2364,7 +2383,7 @@ def clear_all_outfit_covers():
|
||||
|
||||
@app.route('/get_missing_actions')
|
||||
def get_missing_actions():
|
||||
missing = Action.query.filter((Action.image_path == None) | (Action.image_path == '')).all()
|
||||
missing = Action.query.filter((Action.image_path == None) | (Action.image_path == '')).order_by(Action.filename).all()
|
||||
return {'missing': [{'slug': a.slug, 'name': a.name} for a in missing]}
|
||||
|
||||
@app.route('/clear_all_action_covers', methods=['POST'])
|
||||
@@ -2377,7 +2396,7 @@ def clear_all_action_covers():
|
||||
|
||||
@app.route('/get_missing_scenes')
|
||||
def get_missing_scenes():
|
||||
missing = Scene.query.filter((Scene.image_path == None) | (Scene.image_path == '')).all()
|
||||
missing = Scene.query.filter((Scene.image_path == None) | (Scene.image_path == '')).order_by(Scene.filename).all()
|
||||
return {'missing': [{'slug': s.slug, 'name': s.name} for s in missing]}
|
||||
|
||||
@app.route('/clear_all_scene_covers', methods=['POST'])
|
||||
@@ -3739,12 +3758,12 @@ def replace_style_cover_from_preview(slug):
|
||||
|
||||
@app.route('/get_missing_styles')
|
||||
def get_missing_styles():
|
||||
missing = Style.query.filter((Style.image_path == None) | (Style.image_path == '')).all()
|
||||
missing = Style.query.filter((Style.image_path == None) | (Style.image_path == '')).order_by(Style.filename).all()
|
||||
return {'missing': [{'slug': s.slug, 'name': s.name} for s in missing]}
|
||||
|
||||
@app.route('/get_missing_detailers')
|
||||
def get_missing_detailers():
|
||||
missing = Detailer.query.filter((Detailer.image_path == None) | (Detailer.image_path == '')).all()
|
||||
missing = Detailer.query.filter((Detailer.image_path == None) | (Detailer.image_path == '')).order_by(Detailer.filename).all()
|
||||
return {'missing': [{'slug': d.slug, 'name': d.name} for d in missing]}
|
||||
|
||||
@app.route('/clear_all_detailer_covers', methods=['POST'])
|
||||
@@ -3767,7 +3786,7 @@ def clear_all_style_covers():
|
||||
def generate_missing_styles():
|
||||
missing = Style.query.filter(
|
||||
(Style.image_path == None) | (Style.image_path == '')
|
||||
).order_by(Style.name).all()
|
||||
).order_by(Style.filename).all()
|
||||
|
||||
if not missing:
|
||||
flash("No styles missing cover images.")
|
||||
@@ -5119,7 +5138,7 @@ def save_checkpoint_json(slug):
|
||||
|
||||
@app.route('/get_missing_checkpoints')
|
||||
def get_missing_checkpoints():
|
||||
missing = Checkpoint.query.filter((Checkpoint.image_path == None) | (Checkpoint.image_path == '')).all()
|
||||
missing = Checkpoint.query.filter((Checkpoint.image_path == None) | (Checkpoint.image_path == '')).order_by(Checkpoint.checkpoint_path).all()
|
||||
return {'missing': [{'slug': c.slug, 'name': c.name} for c in missing]}
|
||||
|
||||
@app.route('/clear_all_checkpoint_covers', methods=['POST'])
|
||||
@@ -5482,7 +5501,7 @@ def create_look():
|
||||
|
||||
@app.route('/get_missing_looks')
|
||||
def get_missing_looks():
|
||||
missing = Look.query.filter((Look.image_path == None) | (Look.image_path == '')).all()
|
||||
missing = Look.query.filter((Look.image_path == None) | (Look.image_path == '')).order_by(Look.filename).all()
|
||||
return {'missing': [{'slug': l.slug, 'name': l.name} for l in missing]}
|
||||
|
||||
@app.route('/clear_all_look_covers', methods=['POST'])
|
||||
|
||||
Reference in New Issue
Block a user