Add job status tracking and hide poll_interval from UI

This commit is contained in:
alone
2025-07-18 19:16:40 -04:00
parent 7909ef33c8
commit 548da191ff
+25 -146
View File
@@ -1,147 +1,10 @@
# import time
# import requests
# from pathlib import Path
# from sync import Sync
# from sync.common import Audio, Video, GenerationOptions
# from sync.core.api_error import ApiError
# class SyncLipsyncNode:
# @classmethod
# def INPUT_TYPES(cls):
# return {
# "required": {
# "video_url": ("STRING", {"default": ""}),
# "audio_url": ("STRING", {"default": ""}),
# "api_key": ("STRING", {"default": ""}),
# "poll_interval": ("FLOAT", {"default": 5.0, "min": 1.0, "max": 60.0}),
# "model": (["lipsync-2", "lipsync-1.9.0-beta"],),
# "segment_secs": ("STRING", {"default": ""}),
# "segment_frames": ("STRING", {"default": ""}),
# "sync_mode": (["loop", "bounce", "cut_off", "silence", "remap"], {"default": "cut_off"}),
# "temperature": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0}),
# "active_speaker": ("BOOLEAN", {"default": False}),
# }
# }
# RETURN_TYPES = ("STRING",)
# RETURN_NAMES = ("output_path",)
# FUNCTION = "lipsync_generate"
# CATEGORY = "Sync.so"
# OUTPUT_NODE = True
# def lipsync_generate(
# self,
# video_url,
# audio_url,
# api_key,
# poll_interval,
# model,
# segment_secs,
# segment_frames,
# sync_mode,
# temperature,
# active_speaker,
# ):
# client = Sync(base_url="https://api.sync.so", api_key=api_key).generations
# # Prepare video kwargs
# video_kwargs = {}
# if segment_secs:
# try:
# video_kwargs["segments_secs"] = eval(segment_secs)
# except Exception:
# return {
# "ui": {"texts": []},
# "result": ("Invalid format for segment_secs. Example: [[5.0, 10.0]]",)
# }
# if segment_frames:
# try:
# video_kwargs["segments_frames"] = eval(segment_frames)
# except Exception:
# return {
# "ui": {"texts": []},
# "result": ("Invalid format for segment_frames. Example: [[100, 200]]",)
# }
# try:
# response = client.create(
# input=[Video(url=video_url, **video_kwargs), Audio(url=audio_url)],
# model=model,
# options=GenerationOptions(
# sync_mode=sync_mode,
# temperature=temperature,
# active_speaker=active_speaker,
# ),
# )
# except ApiError as e:
# return {
# "ui": {"texts": []},
# "result": (f"Error creating generation: {e.status_code} - {e.body}",)
# }
# job_id = response.id
# status = None
# while status not in ['COMPLETED', 'FAILED']:
# time.sleep(poll_interval)
# generation = client.get(job_id)
# status = generation.status
# if status == 'COMPLETED':
# output_url = generation.output_url
# timestamp = int(time.time())
# output_dir = Path("output")
# output_dir.mkdir(exist_ok=True)
# output_filename = f"sync_output_{timestamp}.mp4"
# output_path = output_dir / output_filename
# try:
# r = requests.get(output_url)
# r.raise_for_status()
# with open(output_path, "wb") as f:
# f.write(r.content)
# except Exception as e:
# return {
# "ui": {"texts": []},
# "result": (f"Failed to download video: {str(e)}",)
# }
# return {
# "ui": {
# "videos": [{
# "filename": output_filename,
# "subfolder": "",
# "type": "output"
# }]
# },
# "result": (str(output_path),)
# }
# else:
# return {
# "ui": {"texts": []},
# "result": (f"Generation failed for job {job_id}",)
# }
# # Node registration for ComfyUI
# NODE_CLASS_MAPPINGS = {
# "SyncLipsyncNode": SyncLipsyncNode,
# }
# NODE_DISPLAY_NAME_MAPPINGS = {
# "SyncLipsyncNode": "Sync.so Lipsync Generator"
# }
# print("✅ Sync.so node with video preview loaded.")
import time, requests
from pathlib import Path
import json
from os.path import getsize
from sync import Sync
from sync.common import Audio, Video, GenerationOptions
# ────────── INPUT NODE
class SyncLipsyncInputNode:
@classmethod
@@ -153,7 +16,6 @@ class SyncLipsyncInputNode:
"video_url": ("STRING", {"default": ""}),
"audio_url": ("STRING", {"default": ""}),
"api_key": ("STRING", {"default": ""}),
"poll_interval": ("FLOAT", {"default": 5.0, "min": 1.0, "max": 60.0}),
}
}
@@ -162,14 +24,14 @@ class SyncLipsyncInputNode:
FUNCTION = "provide"
CATEGORY = "Sync.so/Lipsync"
def provide(self, video_path, audio_path, video_url, audio_url, api_key, poll_interval):
def provide(self, video_path, audio_path, video_url, audio_url, api_key):
return ({
"video_path": video_path,
"audio_path": audio_path,
"video_url": video_url,
"audio_url": audio_url,
"api_key": api_key,
"poll_interval": poll_interval,
"poll_interval": 5.0, # Hidden from UI
},)
@@ -193,7 +55,7 @@ class SyncLipsyncMainNode:
RETURN_NAMES = ("output_path",)
FUNCTION = "lipsync_generate"
CATEGORY = "Sync.so/Lipsync"
def lipsync_generate(self, sync_input, model, segment_secs, segment_frames, sync_mode, temperature, active_speaker):
video_path = sync_input["video_path"]
audio_path = sync_input["audio_path"]
@@ -208,8 +70,11 @@ class SyncLipsyncMainNode:
headers = {"x-api-key": api_key}
try:
job_id = None
status_log = []
if (video_path and Path(video_path).exists() and getsize(video_path) <= MAX_BYTES) or \
(audio_path and Path(audio_path).exists() and getsize(audio_path) <= MAX_BYTES):
(audio_path and Path(audio_path).exists() and getsize(audio_path) <= MAX_BYTES):
print(" Using file upload (v2)")
url = "https://api.sync.so/v2/generate"
data = {
@@ -264,6 +129,14 @@ class SyncLipsyncMainNode:
),
)
job_id = response.id
print(f" Job ID: {job_id}")
# Create job tracking JSON
timestamp = int(time.time())
Path("output").mkdir(exist_ok=True)
json_path = Path("output") / f"sync_job_{timestamp}.json"
with open(json_path, "w") as f:
f.write('{"job_id": "' + job_id + '", "status_log": []}')
# Polling
status_url = f"https://api.sync.so/v2/generate/{job_id}"
@@ -277,6 +150,15 @@ class SyncLipsyncMainNode:
poll.raise_for_status()
status = poll.json().get("status")
print(f" Job status: {status}")
status_log.append(status)
# Update JSON
with open(json_path, "w") as f:
json.dump({
"job_id": job_id,
"status_log": status_log,
"final_status": status
}, f, indent=2)
if status != "COMPLETED":
print(f" Job failed")
@@ -284,9 +166,7 @@ class SyncLipsyncMainNode:
output_url = poll.json().get("outputUrl") or (poll.json().get("result") or {}).get("outputUrl")
print(f" Downloading video from: {output_url}")
timestamp = int(time.time())
output_path = Path("output") / f"sync_output_{timestamp}.mp4"
Path("output").mkdir(exist_ok=True)
r = requests.get(output_url)
r.raise_for_status()
@@ -299,7 +179,6 @@ class SyncLipsyncMainNode:
print(f"🔥 Exception: {e}")
return ("",)
# ────────── OUTPUT NODE
class SyncLipsyncOutputNode:
@classmethod