v1.10.13: Fix Ditto Sampler interruption issue

This commit is contained in:
Hawk Lee
2026-02-02 14:20:52 +08:00
parent b5c2c80e0c
commit de9eefcd64
4 changed files with 99 additions and 16 deletions
+5
View File
@@ -1175,6 +1175,11 @@ B: 太神奇了!那我们快去生成试试吧!
## Changelog
### [1.10.13] - 2026-02-02
- **Ditto Sampler**: 修复了采样过程中无法正常响应 ComfyUI 中断/取消信号的问题。
- 为所有工作线程增加了超时检测和状态轮询,支持在长任务执行期间即时退出。
### [1.10.12] - 2026-02-02
- **Debug & Utilities**: 新增 **Text Debug Splicer** 节点。
+13 -2
View File
@@ -5,6 +5,7 @@ import numpy as np
from PIL import Image
import folder_paths
import logging
import comfy.model_management # Added for interruption check
# Add Ditto library to path
@@ -972,8 +973,18 @@ class AIIA_DittoSampler:
aud_feat = master_sdk.wav2feat.wav2feat(audio_np)
master_sdk.audio2motion_queue.put(aud_feat)
# 5. Wait for completion
# master_sdk.close() joins threads.
# 5. Wait for completion with Interruption Check
# Replace blocking master_sdk.close() with polling
import time
while any(t.is_alive() for t in master_sdk.thread_list):
if comfy.model_management.should_stop():
logger.info("[Ditto] Interruption detected. Stopping workers...")
master_sdk.stop_event.set()
# Flush queues to unblock workers waiting on put
# (Though we already added timeout in SDK, this is faster)
break
time.sleep(0.1)
master_sdk.close()
except Exception as e:
logging.error(f"Error during Ditto inference: {e}")
+80 -13
View File
@@ -295,13 +295,24 @@ class StreamSDK:
except queue.Empty:
continue
if item is None:
self.writer_queue.put(None)
while not self.stop_event.is_set():
try:
self.writer_queue.put(None, timeout=1)
break
except queue.Full:
continue
break
frame_idx, render_img = item
frame_rgb = self.source_info["img_rgb_lst"][frame_idx]
M_c2o = self.source_info["M_c2o_lst"][frame_idx]
res_frame_rgb = self.putback(frame_rgb, render_img, M_c2o)
self.writer_queue.put(res_frame_rgb)
while not self.stop_event.is_set():
try:
self.writer_queue.put(res_frame_rgb, timeout=1)
break
except queue.Full:
continue
def decode_f3d_worker(self):
try:
@@ -317,11 +328,22 @@ class StreamSDK:
except queue.Empty:
continue
if item is None:
self.putback_queue.put(None)
while not self.stop_event.is_set():
try:
self.putback_queue.put(None, timeout=1)
break
except queue.Full:
continue
break
frame_idx, f_3d = item
render_img = self.decode_f3d(f_3d)
self.putback_queue.put([frame_idx, render_img])
while not self.stop_event.is_set():
try:
self.putback_queue.put([frame_idx, render_img], timeout=1)
break
except queue.Full:
continue
def warp_f3d_worker(self):
try:
@@ -337,12 +359,23 @@ class StreamSDK:
except queue.Empty:
continue
if item is None:
self.decode_f3d_queue.put(None)
while not self.stop_event.is_set():
try:
self.decode_f3d_queue.put(None, timeout=1)
break
except queue.Full:
continue
break
frame_idx, x_s, x_d = item
f_s = self.source_info["f_s_lst"][frame_idx]
f_3d = self.warp_f3d(f_s, x_s, x_d)
self.decode_f3d_queue.put([frame_idx, f_3d])
while not self.stop_event.is_set():
try:
self.decode_f3d_queue.put([frame_idx, f_3d], timeout=1)
break
except queue.Full:
continue
def motion_stitch_worker(self):
try:
@@ -358,13 +391,24 @@ class StreamSDK:
except queue.Empty:
continue
if item is None:
self.warp_f3d_queue.put(None)
while not self.stop_event.is_set():
try:
self.warp_f3d_queue.put(None, timeout=1)
break
except queue.Full:
continue
break
frame_idx, x_d_info, ctrl_kwargs = item
x_s_info = self.source_info["x_s_info_lst"][frame_idx]
x_s, x_d = self.motion_stitch(x_s_info, x_d_info, **ctrl_kwargs)
self.warp_f3d_queue.put([frame_idx, x_s, x_d])
while not self.stop_event.is_set():
try:
self.warp_f3d_queue.put([frame_idx, x_s, x_d], timeout=1)
break
except queue.Full:
continue
def audio2motion_worker(self):
try:
@@ -401,6 +445,8 @@ class StreamSDK:
min_silence_for_reset = 30 # ~1.2s @ 25fps. Avoid resetting on standard sentence pauses.
while idx < num_frames:
if self.stop_event.is_set():
break
pbar.update()
vad_timeline = getattr(self, "vad_timeline", None)
@@ -515,7 +561,12 @@ class StreamSDK:
break
self.motion_stitch_queue.put(None)
while not self.stop_event.is_set():
try:
self.motion_stitch_queue.put(None, timeout=1)
break
except queue.Full:
continue
def _audio2motion_worker(self):
@@ -615,14 +666,30 @@ class StreamSDK:
if is_end:
break
self.motion_stitch_queue.put(None)
while not self.stop_event.is_set():
try:
self.motion_stitch_queue.put(None, timeout=1)
break
except queue.Full:
continue
def close(self):
# flush frames
self.audio2motion_queue.put(None)
# flush frames - using non-blocking put as it might be called during interruption
while not self.stop_event.is_set():
try:
self.audio2motion_queue.put(None, timeout=1)
break
except queue.Full:
continue
# Wait for worker threads to finish
for thread in self.thread_list:
thread.join()
while thread.is_alive():
thread.join(timeout=0.1)
# Check ComfyUI interruption if needed (caller should handle this usually)
# but we just want to ensure we don't block forever if stop_event is set elsewhere
if self.stop_event.is_set() and not thread.is_alive():
break
try:
self.writer.close()
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "aiia"
description = "The Ultimate AI Audio/Video toolkit for ComfyUI. Features an enhanced Ditto (with optimizations that outperform official demos and other SOTA talking head models in lip-sync accuracy and natural motion), EchoMimic V3 & FLOAT, VibeVoice & CosyVoice 3.0 (Zero-Shot Voice Cloning), Multi-Role Podcast Generation, and a powerful Media Browser."
version = "1.10.12"
version = "1.10.13"
license = {file = "LICENSE"}
readme = "README.md"
authors = [