Update comfy_engine.py
Browse files- comfy_engine.py +118 -55
comfy_engine.py
CHANGED
|
@@ -68,78 +68,141 @@ def complete_task(task_id, video_url=None, error=None):
|
|
| 68 |
save_history(data)
|
| 69 |
|
| 70 |
# ==========================================
|
| 71 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
# ==========================================
|
| 73 |
-
def inject_params(req: dict) -> dict:
|
| 74 |
-
p = json.loads(json.dumps(WORKFLOW_TEMPLATE))
|
| 75 |
-
p["89"]["inputs"]["text"] = req.get("prompt", "")
|
| 76 |
-
ratio = req.get("aspect_ratio", "16:9")
|
| 77 |
-
quality = req.get("quality", "480p")
|
| 78 |
-
width, height = RESOLUTIONS.get(quality, {}).get(ratio, (848, 480))
|
| 79 |
-
p["74"]["inputs"]["width"] = width
|
| 80 |
-
p["74"]["inputs"]["height"] = height
|
| 81 |
-
p["74"]["inputs"]["length"] = req.get("frames", 81)
|
| 82 |
-
p["88"]["inputs"]["fps"] = req.get("fps", 16)
|
| 83 |
-
|
| 84 |
-
# ❌ यहाँ से मैंने steps और cfg वाली दोनों लाइनें डिलीट कर दी हैं!
|
| 85 |
-
# अब यह कोड सीधा workflow.json की मास्टर सेटिंग्स उठाएगा।
|
| 86 |
-
return p
|
| 87 |
-
|
| 88 |
-
def extract_video_url(history: dict, token: str) -> str:
|
| 89 |
-
outputs = history.get("outputs", {})
|
| 90 |
-
for _, node_out in outputs.items():
|
| 91 |
-
for key in ("videos", "images", "files"):
|
| 92 |
-
if key in node_out and node_out[key]:
|
| 93 |
-
it = node_out[key][0]
|
| 94 |
-
q = urlencode(it)
|
| 95 |
-
return f"/api/video?{q}&token={token}"
|
| 96 |
-
raise RuntimeError("Video not found")
|
| 97 |
-
|
| 98 |
-
async def queue_prompt(req: dict):
|
| 99 |
-
token = str(uuid.uuid4())
|
| 100 |
-
client_id = str(uuid.uuid4())
|
| 101 |
-
prompt_data = inject_params(req)
|
| 102 |
-
|
| 103 |
-
async with httpx.AsyncClient() as client:
|
| 104 |
-
resp = await client.post(f"http://{COMFY_HOST}/prompt?token={token}",
|
| 105 |
-
json={"prompt": prompt_data, "client_id": client_id},
|
| 106 |
-
timeout=30.0)
|
| 107 |
-
prompt_id = resp.json().get("prompt_id")
|
| 108 |
-
if not prompt_id: raise Exception("Failed to queue on AMD Server")
|
| 109 |
-
|
| 110 |
-
return prompt_id, client_id, token, len(prompt_data)
|
| 111 |
-
|
| 112 |
-
# 🚀 THE BACKGROUND WATCHER (यह फोन बंद होने पर भी चलता रहेगा)
|
| 113 |
async def background_watcher(task_id, client_id, token, total_nodes):
|
| 114 |
seen = set()
|
| 115 |
start_t = time.time()
|
| 116 |
progress_fake = 0
|
| 117 |
ws_url = f"ws://{COMFY_HOST}/ws?clientId={client_id}&token={token}"
|
| 118 |
-
|
|
|
|
| 119 |
try:
|
| 120 |
-
|
|
|
|
| 121 |
while True:
|
| 122 |
msg_raw = await ws.recv()
|
|
|
|
| 123 |
if isinstance(msg_raw, (bytes, bytearray)):
|
| 124 |
if progress_fake < 95 and (time.time() - start_t) > 2:
|
| 125 |
progress_fake = min(95, progress_fake + 1)
|
| 126 |
-
update_task_progress(task_id, progress_fake
|
| 127 |
continue
|
| 128 |
-
|
| 129 |
msg = json.loads(msg_raw)
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
node = msg.get("data", {}).get("node")
|
| 132 |
-
if node is None:
|
|
|
|
|
|
|
| 133 |
if node not in seen:
|
| 134 |
seen.add(node)
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
try:
|
| 144 |
async with httpx.AsyncClient() as client:
|
| 145 |
h_resp = await client.get(f"http://{COMFY_HOST}/history/{task_id}?token={token}", timeout=30.0)
|
|
|
|
| 68 |
save_history(data)
|
| 69 |
|
| 70 |
# ==========================================
|
| 71 |
+
# 🗄️ SMART HISTORY MANAGER (Live Tracking)
|
| 72 |
+
# ==========================================
|
| 73 |
+
def load_history():
|
| 74 |
+
if os.path.exists("history.json"):
|
| 75 |
+
try:
|
| 76 |
+
with open("history.json", "r", encoding="utf-8") as f:
|
| 77 |
+
return json.load(f)
|
| 78 |
+
except:
|
| 79 |
+
pass
|
| 80 |
+
return []
|
| 81 |
+
|
| 82 |
+
def save_history(data):
|
| 83 |
+
with open("history.json", "w", encoding="utf-8") as f:
|
| 84 |
+
json.dump(data[:50], f, indent=4) # 50 वीडियोस की लिमिट
|
| 85 |
+
|
| 86 |
+
def init_task(task_id, prompt):
|
| 87 |
+
data = load_history()
|
| 88 |
+
# 👈 फिक्स 1: शुरुआत में इसे 'queued' रखो ताकि UI पर पीला वाला IN QUEUE दिखे
|
| 89 |
+
new_entry = {
|
| 90 |
+
"id": task_id,
|
| 91 |
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 92 |
+
"prompt": prompt,
|
| 93 |
+
"status": "queued",
|
| 94 |
+
"progress": 0,
|
| 95 |
+
"url": None
|
| 96 |
+
}
|
| 97 |
+
data.insert(0, new_entry)
|
| 98 |
+
save_history(data)
|
| 99 |
+
|
| 100 |
+
# 👈 फिक्स 2: प्रोग्रेस के साथ-साथ स्टेटस भी अपडेट करने का ऑप्शन
|
| 101 |
+
def update_task_progress(task_id, progress, status=None):
|
| 102 |
+
data = load_history()
|
| 103 |
+
for item in data:
|
| 104 |
+
if item["id"] == task_id:
|
| 105 |
+
item["progress"] = progress
|
| 106 |
+
if status:
|
| 107 |
+
item["status"] = status
|
| 108 |
+
break
|
| 109 |
+
save_history(data)
|
| 110 |
+
|
| 111 |
+
def complete_task(task_id, video_url=None, error=None):
|
| 112 |
+
data = load_history()
|
| 113 |
+
for item in data:
|
| 114 |
+
if item["id"] == task_id:
|
| 115 |
+
if error:
|
| 116 |
+
item["status"] = "failed"
|
| 117 |
+
item["error"] = error
|
| 118 |
+
else:
|
| 119 |
+
item["status"] = "done"
|
| 120 |
+
item["progress"] = 100
|
| 121 |
+
item["url"] = video_url
|
| 122 |
+
break
|
| 123 |
+
save_history(data)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# ==========================================
|
| 127 |
+
# 🚀 THE BULLETPROOF BACKGROUND WATCHER
|
| 128 |
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
async def background_watcher(task_id, client_id, token, total_nodes):
|
| 130 |
seen = set()
|
| 131 |
start_t = time.time()
|
| 132 |
progress_fake = 0
|
| 133 |
ws_url = f"ws://{COMFY_HOST}/ws?clientId={client_id}&token={token}"
|
| 134 |
+
|
| 135 |
+
# 1. WEBSOCKET LOOP (Live Progress के लिए)
|
| 136 |
try:
|
| 137 |
+
# 👈 टाइमआउट बढ़ा दिया है ताकि जल्दी ना कटे
|
| 138 |
+
async with websockets.connect(ws_url, ping_interval=30, ping_timeout=120) as ws:
|
| 139 |
while True:
|
| 140 |
msg_raw = await ws.recv()
|
| 141 |
+
|
| 142 |
if isinstance(msg_raw, (bytes, bytearray)):
|
| 143 |
if progress_fake < 95 and (time.time() - start_t) > 2:
|
| 144 |
progress_fake = min(95, progress_fake + 1)
|
| 145 |
+
update_task_progress(task_id, progress_fake, "processing")
|
| 146 |
continue
|
| 147 |
+
|
| 148 |
msg = json.loads(msg_raw)
|
| 149 |
+
|
| 150 |
+
# जब GPU असली रेंडरिंग शुरू करे
|
| 151 |
+
if msg.get("type") == "execution_start":
|
| 152 |
+
update_task_progress(task_id, progress_fake, "processing")
|
| 153 |
+
|
| 154 |
+
elif msg.get("type") == "executing":
|
| 155 |
node = msg.get("data", {}).get("node")
|
| 156 |
+
if node is None:
|
| 157 |
+
break # रेंडरिंग ख़तम
|
| 158 |
+
|
| 159 |
if node not in seen:
|
| 160 |
seen.add(node)
|
| 161 |
+
|
| 162 |
+
p_real = int((len(seen) / total_nodes) * 100)
|
| 163 |
+
progress_fake = max(progress_fake, p_real)
|
| 164 |
+
update_task_progress(task_id, progress_fake, "processing")
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
print(f"⚠️ WS Drop for {task_id}: {e} (Switching to HTTP Polling...)")
|
| 168 |
+
pass # अगर फोन कट भी गया, तो नीचे वाला जासूस संभाल लेगा!
|
| 169 |
+
|
| 170 |
+
# 2. HTTP POLLING FALLBACK (असली ब्रह्मास्त्र)
|
| 171 |
+
# यह तब तक बैकएंड से पूछता रहेगा जब तक वीडियो सच में बन नहीं जाता
|
| 172 |
+
while True:
|
| 173 |
+
try:
|
| 174 |
+
async with httpx.AsyncClient() as client:
|
| 175 |
+
# A. सबसे पहले चेक करो कि वीडियो बनकर हिस्ट्री में आ गया क्या?
|
| 176 |
+
h_resp = await client.get(f"http://{COMFY_HOST}/history/{task_id}?token={token}", timeout=10.0)
|
| 177 |
+
if h_resp.status_code == 200:
|
| 178 |
+
history_data = h_resp.json()
|
| 179 |
+
if task_id in history_data:
|
| 180 |
+
v_url = extract_video_url(history_data[task_id], token)
|
| 181 |
+
complete_task(task_id, video_url=v_url)
|
| 182 |
+
return # काम पूरा, लूप ख़तम!
|
| 183 |
+
|
| 184 |
+
# B. अगर नहीं बना, तो चेक करो कि अभी भी कतार में है क्या?
|
| 185 |
+
q_resp = await client.get(f"http://{COMFY_HOST}/queue?token={token}", timeout=10.0)
|
| 186 |
+
if q_resp.status_code == 200:
|
| 187 |
+
queue_data = q_resp.json()
|
| 188 |
+
|
| 189 |
+
is_running = any(req[1] == task_id for req in queue_data.get("queue_running", []))
|
| 190 |
+
is_pending = any(req[1] == task_id for req in queue_data.get("queue_pending", []))
|
| 191 |
+
|
| 192 |
+
if is_running:
|
| 193 |
+
update_task_progress(task_id, progress_fake, "processing")
|
| 194 |
+
elif is_pending:
|
| 195 |
+
update_task_progress(task_id, progress_fake, "queued")
|
| 196 |
+
else:
|
| 197 |
+
# ना हिस्ट्री में है, ना रनिंग है, ना पेंडिंग है! मतलब सर्वर से क्रैश हो गया।
|
| 198 |
+
complete_task(task_id, error="Task disappeared from ComfyUI server.")
|
| 199 |
+
return
|
| 200 |
+
|
| 201 |
+
except Exception as e:
|
| 202 |
+
print(f"⚠️ Polling failed for {task_id}, retrying... ({e})")
|
| 203 |
+
|
| 204 |
+
# 10 सेकंड आराम करके फिर चेक करेगा
|
| 205 |
+
await asyncio.sleep(10)
|
| 206 |
try:
|
| 207 |
async with httpx.AsyncClient() as client:
|
| 208 |
h_resp = await client.get(f"http://{COMFY_HOST}/history/{task_id}?token={token}", timeout=30.0)
|