diff --git a/sync_node.py b/sync_node.py index b32b86e..9953903 100644 --- a/sync_node.py +++ b/sync_node.py @@ -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