Add job status tracking and hide poll_interval from UI
This commit is contained in:
+25
-146
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user