diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/comfyui-llm-node-for-amazon-bedrock.iml b/.idea/comfyui-llm-node-for-amazon-bedrock.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/comfyui-llm-node-for-amazon-bedrock.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8d7435e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 824dba6..7b18caf 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,14 @@ Here are models ready for use, more models are coming soon. - [X] Claude Instant (1.x) - Amazon: + - Nova Canvas + - [X] text to image + - [X] variation + - [X] background replacement(with prompt) + + - Nova Reel + - [X] text to video + - [X] image to video - Titan Image Generator G1 (1.x) - [X] text to image diff --git a/assets/nova_video_output.png b/assets/nova_video_output.png new file mode 100644 index 0000000..c9148f7 Binary files /dev/null and b/assets/nova_video_output.png differ diff --git a/images/edit12.png b/images/edit12.png new file mode 100644 index 0000000..2e2b417 Binary files /dev/null and b/images/edit12.png differ diff --git a/images/edit7.png b/images/edit7.png new file mode 100644 index 0000000..5390d6f Binary files /dev/null and b/images/edit7.png differ diff --git a/images/gen9.png b/images/gen9.png new file mode 100644 index 0000000..a6c68da Binary files /dev/null and b/images/gen9.png differ diff --git a/images/ref7.jpg b/images/ref7.jpg new file mode 100644 index 0000000..8eb7ef4 Binary files /dev/null and b/images/ref7.jpg differ diff --git a/nodes/bedrock.py b/nodes/bedrock.py index be5f154..c103660 100644 --- a/nodes/bedrock.py +++ b/nodes/bedrock.py @@ -20,6 +20,129 @@ CLAUDE3_MAX_SIZE = 1568 bedrock_runtime_client = get_client(service_name="bedrock-runtime") + +class BedrockNovaMultimodal: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "model_id": ( + [ + "amazon.nova-pro-v1:0", + "amazon.nova-lite-v1:0", + ], + ), + "maxTokens": ( + "INT", + { + "default": 2048, + "min": 0, # Minimum value + "max": 4096, # Maximum value + "step": 1, # Slider's step + "display": "number", # Cosmetic only: display as "number" or "slider" + }, + ), + "temperature": ( + "FLOAT", + { + "default": 0.5, + "min": 0.0, + "max": 1.0, + "step": 0.1, + "round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "slider", + }, + ), + "topP": ( + "FLOAT", + { + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.1, + "round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "slider", + }, + ) + }, + "optional": { + "image": ("IMAGE",{"default": None}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "forward" + CATEGORY = "aws" + + @retry(tries=MAX_RETRY) + def forward( + self, + **kwargs + ): + + prompt = kwargs.get('prompt') + model_id = kwargs.get('model_id') + maxTokens = kwargs.get('maxTokens') + temperature = kwargs.get('temperature') + topP = kwargs.get('topP') + image = kwargs.get('image') + + content = [] + content.append({ + "text": prompt, + }) + + if image is not None: + image = image[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + + width, height = image.size + max_size = max(width, height) + if max_size > CLAUDE3_MAX_SIZE: + width = round(width * CLAUDE3_MAX_SIZE / max_size) + height = round(height * CLAUDE3_MAX_SIZE / max_size) + image = image.resize((width, height)) + + buffer = BytesIO() + image.save(buffer, format="png", quality=80) + image_data = buffer.getvalue() + + image_base64 = base64.b64encode(image_data).decode("utf-8") + content.append( { + "image": { + "format": "png", + "source": {"bytes": image_base64}, + } + } + ) + + inf_params = {"max_new_tokens": maxTokens, "top_p": topP, "temperature": temperature} + + + body = json.dumps( + { + "schemaVersion": "messages-v1", + "messages": [ + { + "role": "user", + "content": content, + } + ], + "inferenceConfig": inf_params, + }, + ensure_ascii=False, + ) + + response = bedrock_runtime_client.invoke_model( + body=body, + modelId=model_id, + ) + + message = json.loads(response.get("body").read())['output']['message']['content'][0]['text'] + print("here1===",message) + return (message,) + class BedrockClaudeMultimodal: @classmethod def INPUT_TYPES(s): @@ -252,7 +375,7 @@ class BedrockClaude: # The different model providers have individual request and response formats. # For the format, ranges, and default values for Anthropic Claude, refer to: # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html - + print("prompt input:",prompt) body = json.dumps( { "anthropic_version": "bedrock-2023-05-31", @@ -280,6 +403,7 @@ class BedrockClaude: modelId=model_id, ) message = json.loads(response.get("body").read())["content"][0]["text"] + print("output message:",message) return (message,) @@ -450,4 +574,5 @@ NODE_CLASS_MAPPINGS = { "Bedrock - Claude": BedrockClaude, "Bedrock - Claude Multimodal": BedrockClaudeMultimodal, "Bedrock - SDXL": BedrockSDXL, + "Bedrock - Nova": BedrockNovaMultimodal } diff --git a/nodes/bedrock_nova_image.py b/nodes/bedrock_nova_image.py new file mode 100644 index 0000000..300811a --- /dev/null +++ b/nodes/bedrock_nova_image.py @@ -0,0 +1,454 @@ +import json +import os +import re +import base64 +from io import BytesIO +from random import randint +import logging +from datetime import datetime +import requests +from retry import retry +from PIL import Image +import numpy as np +import torch +from .session import get_client +import boto3 + +MAX_RETRY = 3 + +def generate_images( + inference_params, + model_id="amazon.nova-canvas-v1:0", + region_name="us-east-1", + endpoint_url=None, + output_directory="~/ComfyUI/output", +): + + os.makedirs(output_directory, exist_ok=True) + + image_count = 1 + if "imageGenerationConfig" in inference_params: + if "numberOfImages" in inference_params["imageGenerationConfig"]: + image_count = inference_params["imageGenerationConfig"]["numberOfImages"] + + print(f"Generating {image_count} image(s) with {model_id}") + + # Display the seed value if one is being used. + if "imageGenerationConfig" in inference_params: + if "seed" in inference_params["imageGenerationConfig"]: + print( + f"Using seed: {inference_params['imageGenerationConfig']['seed']}" + ) + + bedrock_client_optional_args = ( + {} if endpoint_url is None else {"endpoint_url": endpoint_url} + ) + bedrock = boto3.client( + service_name="bedrock-runtime", + #region_name=region_name, + **bedrock_client_optional_args, + ) + + body_json = json.dumps(inference_params, indent=2) + #print("here1===",body_json) + + # Write request body to JSON file. + request_file_path = os.path.join(output_directory, "request.json") + with open(request_file_path, "w") as f: + f.write(body_json) + + try: + response = bedrock.invoke_model( + body=body_json, + modelId=model_id, + accept="application/json", + contentType="application/json", + ) + + response_metadata = response.get("ResponseMetadata") + # WRite response metadata to JSON file. + response_metadata_file_path = os.path.join( + output_directory, "response_metadata.json" + ) + with open(response_metadata_file_path, "w") as f: + json.dump(response_metadata, f, indent=2) + + response_body = json.loads(response.get("body").read()) + + # Write response body to JSON file. + response_file_path = os.path.join(output_directory, "response_body.json") + with open(response_file_path, "w") as f: + json.dump(response_body, f, indent=2) + + # Log the request ID. + print(f"Request ID: {response['ResponseMetadata']['RequestId']}") + + # Check for non-exception errors. + if "error" in response_body: + if response_body["error"] == "": + print( + """Response included 'error' of "" (empty string). This indicates a bug with the Bedrock API.""" + ) + else: + printf("Error: {response_body['error']}") + + return response_body + + except Exception as e: + # If e has "response", write it to disk as JSON. + if hasattr(e, "response"): + error_response = e.response + error_response_file_path = os.path.join( + output_directory, "error_response.json" + ) + with open(error_response_file_path, "w") as f: + json.dump(error_response, f, indent=2) + + print(e) + raise e + + +bedrock_runtime_client = get_client(service_name="bedrock-runtime") +output_directory = "/home/ubuntu/ComfyUI/output/" +class BedrockNovaTextImage: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "num_images": ( + "INT", + { + "default": 1, + "min": 1, + "max": 5, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + "quality": ( + [ + "standard", + "premium", + ], + ), + "resolution": ( + [ + "1024 x 1024", + "768 x 768", + "512 x 512", + "1152 x 768", + "576 x 384", + "768 x 1280", + "384 x 640", + "1280 x 768" + ], + ), + "cfg_scale": ( + "FLOAT", + { + "default": 7.0, + "min": 0.0, + "max": 35.0, + "step": 0.1, + "round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "slider", + }, + ), + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 2147483646, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + }, + "optional": { + "negative_prompt":("STRING", {"multiline": True}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "forward" + CATEGORY = "aws" + + @retry(tries=MAX_RETRY) + def forward(self, prompt, negative_prompt,num_images, quality, resolution, cfg_scale, seed): + + height, width = map(int, re.findall(r"\d+", resolution)) + + inference_params ={ + "taskType": "TEXT_IMAGE", + "textToImageParams": {"text": prompt, + "negativeText":negative_prompt}, + "imageGenerationConfig": { + "numberOfImages": num_images, + "quality": quality, + "cfgScale": cfg_scale, + "height": height, + "width": width, + "seed": seed, + }, + } + + # Define an output directory with a unique name. + generation_id = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + output_directory = f"output/{generation_id}" + + # Generate the image(s). + response_body = generate_images( + inference_params=inference_params, + model_id="amazon.nova-canvas-v1:0", + output_directory=output_directory + ) + + # response_body = json.loads(response["body"].read()) + # base64_image_data = response_body["images"][0] + images = [ + np.array(Image.open(BytesIO(base64.b64decode(base64_image)))) + for base64_image in response_body.get("images") + ] + images = [ + torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) + for image in images + ] + if len(images) == 1: + images = images[0] + else: + images = torch.cat(images, 0) + return (images,) + + +class BedrockNovaIpAdatper: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "prompt": ("STRING", {"multiline": True}), + "negative_prompt": ("STRING", {"multiline": True}), + "similarity_strength": ( + "FLOAT", + { + "default": 0.9, + "min": 0.2, + "max": 1.0, + "step": 0.1, + "round": 0.01, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "slider", + }, + ), + "num_images": ( + "INT", + { + "default": 1, + "min": 1, + "max": 5, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + "cfg_scale": ( + "FLOAT", + { + "default": 8.0, + "min": 1.1, + "max": 10.0, + "step": 0.1, + "round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "slider", + }, + ), + "resolution": ( + [ + "1024 x 1024", + "1024 x 1024", + "768 x 768", + "512 x 512", + "1152 x 768", + "576 x 384", + "768 x 1280" + ], + ), + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 2147483646, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "forward" + CATEGORY = "aws" + + @retry(tries=MAX_RETRY) + def forward(self, image, prompt, negative_prompt, similarity_strength, num_images, cfg_scale, resolution,seed): + + image = image[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + + height, width = map(int, re.findall(r"\d+", resolution)) + + buffer = BytesIO() + image.save(buffer, format="PNG") + + image_data = buffer.getvalue() + + image_base64 = base64.b64encode(image_data).decode("utf-8") + + inference_params = { + "taskType": "IMAGE_VARIATION", + "imageVariationParams": { + "images": [image_base64], + "text": prompt, + "negativeText": negative_prompt, + "similarityStrength": similarity_strength, + }, + "imageGenerationConfig": { + "numberOfImages": num_images, + "quality": "premium", + "height": height, + "width": width, + "cfgScale": cfg_scale, + "seed": seed + } + } + + response_body = generate_images( + inference_params=inference_params, + model_id="amazon.nova-canvas-v1:0", + output_directory=output_directory + ) + + images = [ + np.array(Image.open(BytesIO(base64.b64decode(base64_image)))) + for base64_image in response_body.get("images") + ] + images = [ + torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) + for image in images + ] + if len(images) == 1: + images = images[0] + else: + images = torch.cat(images, 0) + return (images,) + + +class BedrockNovaBackgroundPEReplace: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "prompt": ("STRING", {"multiline": True}), + "mask_prompt": ("STRING", {"multiline": True}), + "num_images": ( + "INT", + { + "default": 1, + "min": 1, + "max": 5, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + "cfg_scale": ( + "FLOAT", + { + "default": 8.0, + "min": 1.1, + "max": 10.0, + "step": 0.1, + "round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "slider", + }, + ), + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 2147483646, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "forward" + CATEGORY = "aws" + + @retry(tries=MAX_RETRY) + def forward(self, image, prompt, mask_prompt, num_images, cfg_scale,seed): + image = image[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + + + buffer = BytesIO() + image.save(buffer, format="PNG") + + image_data = buffer.getvalue() + + image_base64 = base64.b64encode(image_data).decode("utf-8") + + inference_params = { + "taskType": "OUTPAINTING", + "outPaintingParams": { + "image": image_base64, + "text": prompt, + "maskPrompt": mask_prompt, + "outPaintingMode": "PRECISE", + }, + "imageGenerationConfig": { + "numberOfImages": num_images, + "quality": "premium", + "cfgScale": cfg_scale, + "seed": seed + } + } + + response_body = generate_images( + inference_params=inference_params, + model_id="amazon.nova-canvas-v1:0", + output_directory=output_directory + ) + + images = [ + np.array(Image.open(BytesIO(base64.b64decode(base64_image)))) + for base64_image in response_body.get("images") + ] + images = [ + torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) + for image in images + ] + if len(images) == 1: + images = images[0] + else: + images = torch.cat(images, 0) + return (images,) + + + +NODE_CLASS_MAPPINGS = { + "Bedrock - Nova Text to Image": BedrockNovaTextImage, + "Bedrock - Nova IpAdapter": BedrockNovaIpAdatper, + "Bedrock - Nova Background Prompt Replace": BedrockNovaBackgroundPEReplace, +} \ No newline at end of file diff --git a/nodes/bedrock_nova_video.py b/nodes/bedrock_nova_video.py new file mode 100644 index 0000000..c53efb4 --- /dev/null +++ b/nodes/bedrock_nova_video.py @@ -0,0 +1,227 @@ +import json +import os +import re +import base64 +from io import BytesIO +from random import randint +import logging +from datetime import datetime +import requests +from retry import retry +from PIL import Image +import numpy as np +import torch +from .session import get_client +import time +import boto3 + +MAX_RETRY = 3 +def get_default_region(): + """获取默认的 AWS 区域""" + session = boto3.Session() + return session.region_name + +def get_account_id(): + """获取当前 AWS 账户 ID""" + sts_client = boto3.client('sts') + return sts_client.get_caller_identity().get('Account') + +def is_video_downloaded_for_invocation_job(invocation_job, output_folder="output"): + """ + This function checks if the video file for the given invocation job has been downloaded. + """ + invocation_arn = invocation_job["invocationArn"] + invocation_id = invocation_arn.split("/")[-1] + folder_name = get_folder_name_for_job(invocation_job) + output_folder = os.path.abspath(f"{output_folder}/{folder_name}") + file_name = f"{invocation_id}.mp4" + local_file_path = os.path.join(output_folder, file_name) + return os.path.exists(local_file_path) + +def get_folder_name_for_job(invocation_job): + invocation_arn = invocation_job["invocationArn"] + invocation_id = invocation_arn.split("/")[-1] + submit_time = invocation_job["submitTime"] + timestamp = submit_time.astimezone().strftime("%Y-%m-%d_%H-%M-%S") + folder_name = f"{timestamp}_{invocation_id}" + return folder_name + +def download_video_for_invocation_arn(invocation_arn, bucket_name, destination_folder): + """ + This function downloads the video file for the given invocation ARN. + """ + invocation_id = invocation_arn.split("/")[-1] + + # Create the local file path + file_name = f"{invocation_id}.mp4" + import os + + output_folder = os.path.abspath(destination_folder) + local_file_path = os.path.join(output_folder, file_name) + + # Ensure the output folder exists + os.makedirs(output_folder, exist_ok=True) + + # Create an S3 client + s3 = boto3.client("s3") + + # List objects in the specified folder + response = s3.list_objects_v2(Bucket=bucket_name, Prefix=invocation_id) + + # Find the first MP4 file and download it. + for obj in response.get("Contents", []): + object_key = obj["Key"] + if object_key.endswith(".mp4"): + print(f"""Downloading "{object_key}"...""") + s3.download_file(bucket_name, object_key, local_file_path) + print(f"Downloaded to {local_file_path}") + return local_file_path + + # If we reach this point, no MP4 file was found. + print(f"Problem: No MP4 file was found in S3 at {bucket_name}/{invocation_id}") + +def get_job_id_from_arn(invocation_arn): + return invocation_arn.split("/")[-1] + +def save_completed_job(job, output_folder="output"): + job_id = get_job_id_from_arn(job["invocationArn"]) + + output_folder_abs = os.path.abspath( + f"{output_folder}/{get_folder_name_for_job(job)}" + ) + + # Ensure the output folder exists + os.makedirs(output_folder_abs, exist_ok=True) + + status_file = os.path.join(output_folder_abs, "completed.json") + + if is_video_downloaded_for_invocation_job(job, output_folder=output_folder): + print(f"Skipping completed job {job_id}, video already downloaded.") + return + + s3_bucket_name = ( + job["outputDataConfig"]["s3OutputDataConfig"]["s3Uri"] + .split("//")[1] + .split("/")[0] + ) + + localPath = download_video_for_invocation_arn( + job["invocationArn"], s3_bucket_name, output_folder_abs + ) + return localPath + + +bedrock_runtime_client = get_client(service_name="bedrock-runtime") +region = get_default_region() +account_id = get_account_id() +s3_destination_bucket = f"sagemaker-{region}-{account_id}" + +class BedrockNovaVideo: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "dimension": ( + [ + "1280x720" + ], + ), + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 2147483646, + "step": 1, + "round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. + "display": "number", + }, + ), + }, + "optional": { + "image": ("IMAGE",), + }, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "forward" + CATEGORY = "aws" + + @retry(tries=MAX_RETRY) + def forward(self, **kwargs): + prompt = kwargs.get('prompt') + dimension = kwargs.get('dimension') + seed = kwargs.get('seed') + image = kwargs.get('image') + + + input_image_base64=None + if image is not None: + image = image[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + buffer = BytesIO() + image.save(buffer, format="PNG") + + image_data = buffer.getvalue() + input_image_base64 = base64.b64encode(image_data).decode("utf-8") + + textToVideoParams={ "text": prompt} + if input_image_base64 is not None: + textToVideoParams["images"] = [ + { + "format": "png", # May be "png" or "jpeg" + "source": { + "bytes": input_image_base64 + } + } + ] + model_input = { + "taskType": "TEXT_VIDEO", + "textToVideoParams": textToVideoParams, + "videoGenerationConfig": { + "durationSeconds": 6, # 6 is the only supported value currently. + "fps": 24, # 24 is the only supported value currently. + "dimension": dimension, # "1280x720" is the only supported value currently. + "seed": seed # A random seed guarantees we'll get a different result each time this code runs. + }, + } + + + invocation = bedrock_runtime_client.start_async_invoke( + modelId="amazon.nova-reel-v1:0", + modelInput=model_input, + outputDataConfig={"s3OutputDataConfig": {"s3Uri": f"s3://{s3_destination_bucket}"}}, + ) + + invocation_arn = invocation["invocationArn"] + print("\nResponse:") + print(json.dumps(invocation, indent=2, default=str)) + + save_local_path = "" + + # Save the invocation details for later reference. Helpful for debugging and reporting feedback. + while True: + job_update = bedrock_runtime_client.get_async_invoke(invocationArn=invocation_arn) + status = job_update["status"] + start_time = time.time() + + if status == "Completed": + save_local_path = save_completed_job(job_update, output_folder="/home/ubuntu/ComfyUI/output/") + break + else: + elapsed_time = time.time() - start_time + if elapsed_time > 600: + print("Job timed out after 60 seconds.") + break + else: + time.sleep(5) + return (save_local_path,) + + + + + +NODE_CLASS_MAPPINGS = { + "Bedrock - Nova Video": BedrockNovaVideo +} \ No newline at end of file diff --git a/nodes/file_utils.py b/nodes/file_utils.py new file mode 100644 index 0000000..5643b21 --- /dev/null +++ b/nodes/file_utils.py @@ -0,0 +1,62 @@ +import base64 +import io +import os +from datetime import datetime + +from PIL import Image + + +def save_base64_image(base64_image, output_directory, base_name="image", suffix="_1"): + """ + Saves a base64 encoded image to a specified output directory with a timestamp and a suffix. + + Args: + base64_image (str): The base64 encoded image string. + output_directory (str): The directory where the image will be saved. + suffix (str, optional): A suffix to be added to the filename. Defaults to "_1". + Returns: + PIL.Image.Image: The Pillow Image object representing the saved image. + """ + image_bytes = base64.b64decode(base64_image) + image = Image.open(io.BytesIO(image_bytes)) + save_image(image, output_directory, base_name, suffix) + return image + + +def save_image(image, output_directory, base_name="image", suffix="_1"): + """ + Saves a Pillow Image object to a specified output directory with a timestamp and a suffix. + + Args: + image (PIL.Image.Image): The Pillow Image object to be saved. + output_directory (str): The directory where the image will be saved. + suffix (str, optional): A suffix to be added to the filename. Defaults to "_1". + Returns: + None + """ + if not os.path.exists(output_directory): + os.makedirs(output_directory) + + file_name = f"{base_name}{suffix}.png" + file_path = os.path.join(output_directory, file_name) + image.save(file_path) + + +def save_base64_images(base64_images, output_directory, base_name="image"): + """ + Saves a list of base64 encoded images to a specified output directory. + + Args: + base64_images (list): A list of base64 encoded image strings. + output_directory (str): The directory where the images will be saved. + Returns: + An array of Pillow Image objects representing the saved images. + """ + images = [] + for i, base64_image in enumerate(base64_images): + image = save_base64_image( + base64_image, output_directory, base_name=base_name, suffix=f"_{i+1}" + ) + images.append(image) + + return images diff --git a/nodes/prompts.py b/nodes/prompts.py index 7ebe1c0..7369bbf 100644 --- a/nodes/prompts.py +++ b/nodes/prompts.py @@ -18,6 +18,7 @@ class PromptTemplate: def forward(self, prompt, prompt_template): output = prompt_template.replace("{prompt}", prompt).replace("[prompt]", prompt) + #print("output prompt:",output) return (output,) class PromptTemplateTwoInput: diff --git a/nodes/textract.py b/nodes/textract.py new file mode 100644 index 0000000..ff5f9b1 --- /dev/null +++ b/nodes/textract.py @@ -0,0 +1,423 @@ +import json +import os +import re +import base64 +from io import BytesIO +import io +import requests +from retry import retry +import boto3 +from PIL import Image +import numpy as np +import torch +import cv2 +import folder_paths +from .session import get_client +import torch +from torchvision import transforms +from paddleocr import PaddleOCR, draw_ocr +from paddleocr import PPStructure,draw_structure_result +import tempfile + + +current_directory = os.path.dirname(os.path.abspath(__file__)) +temp_img_path = os.path.join(current_directory, "temp_dir", "AnyText_manual_mask_pos_img.png") +textract = get_client(service_name="textract") +MAX_RETRY = 3 + + +class ImageOCRByTextract: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",) + } + } + + RETURN_TYPES = ("STRING","INT","INT","INT","INT","IMAGE") + RETURN_NAMES = ("Text","Left","Top","Width","Height","Mask Image") + FUNCTION = "forward" + CATEGORY = "aws" + OUTPUT_NODE = True + + def ocr_by_textract(self,image_input): + numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy() + image = image_input[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + # Get the original dimensions of the image + img_width, img_height = image.size + + ## Call Textract DetectDocumentText function + byte_stream = io.BytesIO() + image.save(byte_stream, format='PNG') + byte_image = byte_stream.getvalue() + response = textract.detect_document_text(Document={'Bytes': byte_image}) + + #initial the scan result + result = [] + # Create a black mask image of the same size as the original image + mask = np.zeros_like(numpy_image) + all_text="" + + + # Extract text and bounding box information + for item in response['Blocks']: + if item['BlockType'] == 'LINE': + text = item['Text'] + box = item['Geometry']['BoundingBox'] + left = int(box['Left'] * img_width) + top = int(box['Top'] * img_height) + width = int(box['Width'] * img_width) + height = int(box['Height']* img_height) + + ##Add information to the result list + result.append({ + 'Text': text, + 'Left': left, + 'Top': top, + 'Width': width, + 'Height': height + + }) + all_text=all_text+text + #Draw mask for each text information box + #Specify the coordinates of the top-left and bottom-right corners of the rectangle + temp_mask = np.zeros_like(numpy_image) + x1, y1 = int(left), int(top) + x2, y2 = int(left + width), int(top + height) + # Draw white rectangular boxes on the mask image + cv2.rectangle(temp_mask, (x1, y1), (x2, y2), (255, 255, 255), -1) + mask = cv2.bitwise_or(mask, temp_mask) + + # Perform bitwise operation between the mask image and the original image to generate the masked image + masked_img = cv2.bitwise_and(numpy_image, mask) + masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0) + + print("result",result) + + return all_text,result[0]['Left'],result[0]['Top'],result[0]['Width'],result[0]['Height'],masked_img + + + @retry(tries=MAX_RETRY) + def forward(self, image): + return self.ocr_by_textract(image) + + +### for anyText Nodes +class ImageOCRByTextractV2: + + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] + return {"required": + { + "image": (sorted(files), {"image_upload": True}), + }, + } + + RETURN_TYPES = ("STRING","STRING","STRING","IMAGE") + RETURN_NAMES = ("Texts","Original Image Path","Mask Image Path","AnyText Mask Image") + FUNCTION = "forward" + CATEGORY = "aws" + OUTPUT_NODE = True + + def ocr_by_textract(self,image_input): + ori_image_path = folder_paths.get_annotated_filepath(image_input) + pos_img_path = os.path.join(temp_img_path) + + ## Convert orignal image to tensor + pil_image = Image.open(ori_image_path) + transform = transforms.Compose([ + transforms.ToTensor(), # Convert PIL Image to tensor and normalize pixel values to [0, 1] + ]) + image_input = transform(pil_image) + + ## The image input is already in the standard ComfyUI image tensor format + numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy() + image = image_input[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + # Get the original dimensions of the image + img_width, img_height = image.size + + # Call the Textract DetectDocumentText function + byte_stream = io.BytesIO() + image.save(byte_stream, format='PNG') + byte_image = byte_stream.getvalue() + response = textract.detect_document_text(Document={'Bytes': byte_image}) + + # Initialize the results list + result = [] + # Create a black mask image with the same size as the original image + #mask = np.zeros_like(numpy_image) + mask = np.ones_like(numpy_image) * 255 + all_text="" + + + # Extract text and bounding box information + for item in response['Blocks']: + if item['BlockType'] == 'LINE': + text = item['Text'] + box = item['Geometry']['BoundingBox'] + left = int(box['Left'] * img_width) + top = int(box['Top'] * img_height) + width = int(box['Width'] * img_width) + height = int(box['Height']* img_height) + + # Add the information to the result list + result.append(text) + # Draw mask for each text information box + # Specify the coordinates of the top-left and bottom-right corners of the rectangle + temp_mask = np.zeros_like(numpy_image) + x1, y1 = int(left), int(top) + x2, y2 = int(left + width), int(top + height) + # Draw black rectangular boxes on the mask image + #cv2.rectangle(temp_mask, (x1, y1), (x2, y2), (0, 0, 0), -1) + #mask = cv2.bitwise_or(mask, temp_mask) + + cv2.rectangle(mask, (left, top), (left + width, top + height), (0, 0, 0), -1) + + # Perform bitwise operation between the mask image and the original image to generate the masked image + masked_img = np.where(mask == 0, 0, numpy_image) + #masked_img = cv2.bitwise_and(numpy_image, mask) + masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0) + #masked_img.save(temp_img_path) + # Convert PyTorch tensor to PIL image + to_pil = transforms.ToPILImage() + pil_image = to_pil(masked_img.squeeze(0)) + # save PIL image + pil_image.save(temp_img_path) + + all_text="|".join(result) + print("result",result) + + return all_text ,ori_image_path,temp_img_path, masked_img + + + + @retry(tries=MAX_RETRY) + def forward(self, image): + return self.ocr_by_textract(image) + + +## for layer style nodes +class ImageOCRByTextractV4: + + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] + return {"required":{ + "image": ("IMAGE",), + } + } + + RETURN_TYPES = ("STRING","STRING","STRING","STRING","STRING","STRING","STRING","IMAGE","IMAGE") + RETURN_NAMES = ("Texts","x_offsets","y_offsets","widths","heights","img_width","img_height","Mask Image","Original Image") + FUNCTION = "forward" + CATEGORY = "aws" + OUTPUT_NODE = True + + def convert_to_xywh(self,coordinates): + # Extract all x and y coordinates + x_coords = [coord[0] for coord in coordinates] + y_coords = [coord[1] for coord in coordinates] + + # Calculate x offset and y offset + x_offset = min(x_coords) + y_offset = min(y_coords) + + # Calculate width and height + width = max(x_coords) - x_offset + height = max(y_coords) - y_offset + + print(x_offset,y_offset,width,height) + return int(x_offset), int(y_offset), int(width), int(height) + + def ocr_by_paddleocr(self,image_input): + + image = image_input[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy() + + img_width, img_height = image.size + + + # Create temporary file + with tempfile.NamedTemporaryFile(delete=False, suffix='.png', dir='/tmp/') as temp_file: + temp_filename = temp_file.name + + # Save numpy_image as a temporary file + cv2.imwrite(temp_filename, cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR)) + + ocr = PaddleOCR( + det_model_dir='/home/ubuntu/ComfyUI/models/checkpoints/PaddleOCR/det', + rec_model_dir='/home/ubuntu/ComfyUI/models/checkpoints/PaddleOCR/rec', + det_limit_side_len=2048, + use_angle_cls=True, + #use_gpu=False, + ) + + ocr_results = ocr.ocr(temp_filename, cls=True)[0] + + # Create a mask image with the same size as the original image + result = [] + masked_img = numpy_image.copy() + all_text="" + x_offsets=[] + y_offsets=[] + widths=[] + heights=[] + + + # Extract text and bounding box information + for line in ocr_results: + if not isinstance(line, list): + continue + boxes = line[0] + x_offset,y_offset,width,height = self.convert_to_xywh(boxes) + x_offsets.append(str(x_offset)) + y_offsets.append(str(y_offset)) + widths.append(str(width)) + heights.append(str(height)) + + text = line[1][0] + print("text") + print(text) + result.append(text) + + # Draw mask for each text information box + # Specify the coordinates of the top-left and bottom-right corners of the rectangle + x1, y1 = int(x_offset), int(y_offset) + x2, y2 = int(x_offset + width), int(y_offset + height) + # Draw a black rectangle on the mask image + cv2.rectangle(masked_img, (x1, y1), (x2, y2), (0, 0, 0), -1) + + masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0) + + all_text="|".join(result) + x_offsets="|".join(x_offsets) + y_offsets="|".join(y_offsets) + widths="|".join(widths) + heights="|".join(heights) + + print("result",result) + + # Add original image output + original_img = image_input + # delete temp files + os.unlink(temp_filename) + + return all_text ,x_offsets,y_offsets,widths,heights,img_width,img_height, masked_img,original_img + + + + @retry(tries=MAX_RETRY) + def forward(self, image): + return self.ocr_by_paddleocr(image) + + +## for layer style nodes +class ImageOCRByTextractV3: + + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] + return {"required":{ + "image": ("IMAGE",), + } + } + + RETURN_TYPES = ("STRING","STRING","STRING","STRING","STRING","STRING","STRING","IMAGE","IMAGE") + RETURN_NAMES = ("Texts","x_offsets","y_offsets","widths","heights","img_width","img_height","Mask Image","Original Image") + FUNCTION = "forward" + CATEGORY = "aws" + OUTPUT_NODE = True + + def ocr_by_textract(self,image_input): + + + image = image_input[0] * 255.0 + image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8)) + numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy() + + + img_width, img_height = image.size + + + byte_stream = io.BytesIO() + image.save(byte_stream, format='PNG') + byte_image = byte_stream.getvalue() + response = textract.detect_document_text(Document={'Bytes': byte_image}) + + + result = [] + + masked_img = numpy_image.copy() + all_text="" + x_offsets=[] + y_offsets=[] + widths=[] + heights=[] + + + + for item in response['Blocks']: + if item['BlockType'] == 'LINE': + text = item['Text'] + box = item['Geometry']['BoundingBox'] + + left = int(box['Left'] * img_width) + x_offsets.append(str(left)) + + top = int(box['Top'] * img_height) + y_offsets.append(str(top)) + + width = int(box['Width'] * img_width) + widths.append(str(width)) + + height = int(box['Height']* img_height) + heights.append(str(height)) + + + result.append(text) + # Draw mask for each text information box + # Specify the coordinates of the top-left and bottom-right corners of the rectangle + x1, y1 = int(left), int(top) + x2, y2 = int(left + width), int(top + height) + # Draw black rectangular boxes on the mask image + cv2.rectangle(masked_img, (x1, y1), (x2, y2), (0, 0, 0), -1) + + + #masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0) + masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0) + ###summary the output + all_text="|".join(result) + x_offsets="|".join(x_offsets) + y_offsets="|".join(y_offsets) + widths="|".join(widths) + heights="|".join(heights) + + print("result",result) + + # Add original image output + original_img = image_input + + return all_text ,x_offsets,y_offsets,widths,heights,img_width,img_height, masked_img,original_img + + + + @retry(tries=MAX_RETRY) + def forward(self, image): + return self.ocr_by_textract(image) + + +NODE_CLASS_MAPPINGS = { + "Image OCR By Textract": ImageOCRByTextract, + "Image OCR By Textract V2":ImageOCRByTextractV2, + "Image OCR By Textract V3":ImageOCRByTextractV3, + "Image OCR by PaddleOCR": ImageOCRByTextractV4 +} + diff --git a/workflows/claude3_image_caption.json b/workflows/claude3_image_caption.json deleted file mode 100644 index 431c562..0000000 --- a/workflows/claude3_image_caption.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "last_node_id": 5, - "last_link_id": 3, - "nodes": [ - { - "id": 1, - "type": "Image From URL", - "pos": [ - 68, - 283 - ], - "size": { - "0": 315, - "1": 58 - }, - "flags": {}, - "order": 0, - "mode": 0, - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 1, - 2 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Image From URL" - }, - "widgets_values": [ - "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg" - ] - }, - { - "id": 3, - "type": "Bedrock - Claude Multimodal", - "pos": [ - 461, - 123 - ], - "size": { - "0": 400, - "1": 200 - }, - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 2 - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": [ - 3 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Bedrock - Claude Multimodal" - }, - "widgets_values": [ - "Describe this image.", - "anthropic.claude-3-haiku-20240307-v1:0", - 2048, - 0.5, - 1, - 250 - ] - }, - { - "id": 2, - "type": "PreviewImage", - "pos": [ - 484, - 400 - ], - "size": [ - 266.63671875, - 246 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 1 - } - ], - "properties": { - "Node name for S&R": "PreviewImage" - } - }, - { - "id": 5, - "type": "ShowText|pysssss", - "pos": [ - 916, - 154 - ], - "size": [ - 323.16015625, - 179.2890625 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "name": "text", - "type": "STRING", - "link": 3, - "widget": { - "name": "text" - } - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": null, - "shape": 6 - } - ], - "properties": { - "Node name for S&R": "ShowText|pysssss" - }, - "widgets_values": [ - "", - "The image shows a close-up view of a black ant on a textured surface. The ant appears to be in a crouched or alert position, with its legs and antennae extended. The lighting and focus create a dramatic, almost cinematic effect, highlighting the intricate details of the ant's body and the rough texture of the surface it is standing on." - ] - } - ], - "links": [ - [ - 1, - 1, - 0, - 2, - 0, - "IMAGE" - ], - [ - 2, - 1, - 0, - 3, - 0, - "IMAGE" - ], - [ - 3, - 3, - 0, - 5, - 0, - "STRING" - ] - ], - "groups": [], - "config": {}, - "extra": { - "ds": { - "scale": 1, - "offset": [ - -0.125, - 0.2421875 - ] - } - }, - "version": 0.4 -} \ No newline at end of file diff --git a/workflows/lama.json b/workflows/lama.json new file mode 100644 index 0000000..4e366ea --- /dev/null +++ b/workflows/lama.json @@ -0,0 +1,788 @@ +{ + "last_node_id": 35, + "last_link_id": 71, + "nodes": [ + { + "id": 15, + "type": "PreviewImage", + "pos": [ + 1680, + 650 + ], + "size": { + "0": 295.9647216796875, + "1": 320.621337890625 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 23 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 29, + "type": "PreviewImage", + "pos": [ + 1680, + 1040 + ], + "size": { + "0": 295.9647216796875, + "1": 320.621337890625 + }, + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 62 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 8, + "type": "PreviewImage", + "pos": [ + 1680, + 230 + ], + "size": { + "0": 299.51416015625, + "1": 327.3353271484375 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 8 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 31, + "type": "PreviewImage", + "pos": [ + 2410, + 240 + ], + "size": { + "0": 299.51416015625, + "1": 327.3353271484375 + }, + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 65 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 33, + "type": "PreviewImage", + "pos": [ + 2410, + 630 + ], + "size": { + "0": 299.51416015625, + "1": 327.3353271484375 + }, + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 68 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 35, + "type": "PreviewImage", + "pos": [ + 2420, + 1020 + ], + "size": { + "0": 299.51416015625, + "1": 327.3353271484375 + }, + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 71 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 25, + "type": "Reroute", + "pos": [ + 1132, + 727 + ], + "size": [ + 75, + 26 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "", + "type": "*", + "link": 54 + } + ], + "outputs": [ + { + "name": "", + "type": "MASK", + "links": [ + 58, + 59, + 61, + 64, + 67, + 70 + ], + "slot_index": 0 + } + ], + "properties": { + "showOutputText": false, + "horizontal": false + } + }, + { + "id": 27, + "type": "Reroute", + "pos": [ + 1129, + 439 + ], + "size": [ + 75, + 26 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "", + "type": "*", + "link": 55 + } + ], + "outputs": [ + { + "name": "", + "type": "IMAGE", + "links": [ + 56, + 57, + 60, + 63, + 66, + 69 + ], + "slot_index": 0 + } + ], + "properties": { + "showOutputText": false, + "horizontal": false + } + }, + { + "id": 24, + "type": "LayerMask: RemBgUltra", + "pos": [ + 785, + 772 + ], + "size": { + "0": 315, + "1": 150 + }, + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 36 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": null, + "shape": 3 + }, + { + "name": "mask", + "type": "MASK", + "links": [ + 54 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LayerMask: RemBgUltra" + }, + "widgets_values": [ + 8, + 0.01, + 0.99, + false + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 431, + 439 + ], + "size": { + "0": 323.62158203125, + "1": 420.20391845703125 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 36, + 55 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512with_background (24).png", + "image" + ] + }, + { + "id": 7, + "type": "LayerUtility: LaMa", + "pos": [ + 1300, + 320 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 56 + }, + { + "name": "mask", + "type": "MASK", + "link": 58 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 8 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "lama", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 14, + "type": "LayerUtility: LaMa", + "pos": [ + 1300, + 710 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 57 + }, + { + "name": "mask", + "type": "MASK", + "link": 59 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 23 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "ldm", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 28, + "type": "LayerUtility: LaMa", + "pos": [ + 1290, + 1030 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 60 + }, + { + "name": "mask", + "type": "MASK", + "link": 61 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 62 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "zits", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 30, + "type": "LayerUtility: LaMa", + "pos": [ + 2036, + 314 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 63 + }, + { + "name": "mask", + "type": "MASK", + "link": 64 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 65 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "mat", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 32, + "type": "LayerUtility: LaMa", + "pos": [ + 2037, + 724 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 66 + }, + { + "name": "mask", + "type": "MASK", + "link": 67 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 68 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "fcf", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 34, + "type": "LayerUtility: LaMa", + "pos": [ + 2043, + 1042 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 69 + }, + { + "name": "mask", + "type": "MASK", + "link": 70 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 71 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "spread", + "cuda", + false, + 25, + 6 + ] + } + ], + "links": [ + [ + 8, + 7, + 0, + 8, + 0, + "IMAGE" + ], + [ + 23, + 14, + 0, + 15, + 0, + "IMAGE" + ], + [ + 36, + 2, + 0, + 24, + 0, + "IMAGE" + ], + [ + 54, + 24, + 1, + 25, + 0, + "*" + ], + [ + 55, + 2, + 0, + 27, + 0, + "*" + ], + [ + 56, + 27, + 0, + 7, + 0, + "IMAGE" + ], + [ + 57, + 27, + 0, + 14, + 0, + "IMAGE" + ], + [ + 58, + 25, + 0, + 7, + 1, + "MASK" + ], + [ + 59, + 25, + 0, + 14, + 1, + "MASK" + ], + [ + 60, + 27, + 0, + 28, + 0, + "IMAGE" + ], + [ + 61, + 25, + 0, + 28, + 1, + "MASK" + ], + [ + 62, + 28, + 0, + 29, + 0, + "IMAGE" + ], + [ + 63, + 27, + 0, + 30, + 0, + "IMAGE" + ], + [ + 64, + 25, + 0, + 30, + 1, + "MASK" + ], + [ + 65, + 30, + 0, + 31, + 0, + "IMAGE" + ], + [ + 66, + 27, + 0, + 32, + 0, + "IMAGE" + ], + [ + 67, + 25, + 0, + 32, + 1, + "MASK" + ], + [ + 68, + 32, + 0, + 33, + 0, + "IMAGE" + ], + [ + 69, + 27, + 0, + 34, + 0, + "IMAGE" + ], + [ + 70, + 25, + 0, + 34, + 1, + "MASK" + ], + [ + 71, + 34, + 0, + 35, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} diff --git a/workflows/sensitive erase.json b/workflows/sensitive erase.json new file mode 100644 index 0000000..340f98a --- /dev/null +++ b/workflows/sensitive erase.json @@ -0,0 +1,701 @@ +{ + "last_node_id": 52, + "last_link_id": 83, + "nodes": [ + { + "id": 25, + "type": "PreviewImage", + "pos": [ + 1789, + 644 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 47 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 23, + "type": "Image OCR By Textract", + "pos": [ + -654, + 420 + ], + "size": { + "0": 210, + "1": 126 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 36 + } + ], + "outputs": [ + { + "name": "Text", + "type": "STRING", + "links": [ + 38 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "Left", + "type": "INT", + "links": [], + "shape": 3, + "slot_index": 1 + }, + { + "name": "Top", + "type": "INT", + "links": [], + "shape": 3, + "slot_index": 2 + }, + { + "name": "Width", + "type": "INT", + "links": [], + "shape": 3, + "slot_index": 3 + }, + { + "name": "Height", + "type": "INT", + "links": [], + "shape": 3, + "slot_index": 4 + }, + { + "name": "Mask Image", + "type": "IMAGE", + "links": [ + 49 + ], + "shape": 3, + "slot_index": 5 + } + ], + "properties": { + "Node name for S&R": "Image OCR By Textract" + } + }, + { + "id": 15, + "type": "Bedrock - Claude", + "pos": [ + -322, + 948 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "prompt", + "type": "STRING", + "link": 30, + "widget": { + "name": "prompt" + } + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 59 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "Bedrock - Claude" + }, + "widgets_values": [ + "", + "anthropic.claude-3-haiku-20240307-v1:0", + 2048, + 1, + 1, + 71 + ] + }, + { + "id": 40, + "type": "String", + "pos": [ + 347, + 1165 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 58 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "String" + }, + "widgets_values": [ + "Yes" + ] + }, + { + "id": 48, + "type": "MaskPreview+", + "pos": [ + 756, + 302 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 69 + } + ], + "properties": { + "Node name for S&R": "MaskPreview+" + } + }, + { + "id": 39, + "type": "Compare", + "pos": [ + 757, + 1079 + ], + "size": { + "0": 315, + "1": 78 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "a", + "type": "*", + "link": 59 + }, + { + "name": "b", + "type": "*", + "link": 58 + } + ], + "outputs": [ + { + "name": "B", + "type": "BOOLEAN", + "links": [ + 78 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "Compare" + }, + "widgets_values": [ + "a == b" + ] + }, + { + "id": 32, + "type": "ImageToMask", + "pos": [ + -296, + 518 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 49 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 50, + 79 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageToMask" + }, + "widgets_values": [ + "green" + ] + }, + { + "id": 51, + "type": "If ANY execute A else B", + "pos": [ + 947, + 648 + ], + "size": { + "0": 210, + "1": 66 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "ANY", + "type": "*", + "link": 78 + }, + { + "name": "IF_TRUE", + "type": "*", + "link": 79 + }, + { + "name": "IF_FALSE", + "type": "*", + "link": 80 + } + ], + "outputs": [ + { + "name": "?", + "type": "*", + "links": [ + 81 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "If ANY execute A else B" + } + }, + { + "id": 24, + "type": "LayerUtility: LaMa", + "pos": [ + 1384, + 959 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 83 + }, + { + "name": "mask", + "type": "MASK", + "link": 81, + "slot_index": 1 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 47 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "lama", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 47, + "type": "SolidMask", + "pos": [ + -312, + 227 + ], + "size": { + "0": 315, + "1": 106 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 69, + 80 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0, + 1000, + 662 + ] + }, + { + "id": 33, + "type": "MaskPreview+", + "pos": [ + 511, + 394 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 50 + } + ], + "properties": { + "Node name for S&R": "MaskPreview+" + } + }, + { + "id": 17, + "type": "Prompt Template", + "pos": [ + -400, + 648 + ], + "size": { + "0": 521.4636840820312, + "1": 208.76742553710938 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "prompt", + "type": "STRING", + "link": 38, + "widget": { + "name": "prompt" + } + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 30 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "Prompt Template" + }, + "widgets_values": [ + "", + "判断如下文本中是否包含暴力,恐怖,色情,政治的内容\n[prompt]\n如果包含,返回\"Yes\", 否则返回\"No\"\n直接返回结果,不要包含其他任务内容" + ] + }, + { + "id": 4, + "type": "LoadImage", + "pos": [ + -1091, + 346 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 36, + 82 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "textract_demo005 (1).png", + "image" + ] + }, + { + "id": 52, + "type": "Reroute", + "pos": [ + 726.544712798572, + 819.4428242873535 + ], + "size": [ + 75, + 26 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "", + "type": "*", + "link": 82 + } + ], + "outputs": [ + { + "name": "", + "type": "IMAGE", + "links": [ + 83 + ], + "slot_index": 0 + } + ], + "properties": { + "showOutputText": false, + "horizontal": false + } + } + ], + "links": [ + [ + 30, + 17, + 0, + 15, + 0, + "STRING" + ], + [ + 36, + 4, + 0, + 23, + 0, + "IMAGE" + ], + [ + 38, + 23, + 0, + 17, + 0, + "STRING" + ], + [ + 47, + 24, + 0, + 25, + 0, + "IMAGE" + ], + [ + 49, + 23, + 5, + 32, + 0, + "IMAGE" + ], + [ + 50, + 32, + 0, + 33, + 0, + "MASK" + ], + [ + 58, + 40, + 0, + 39, + 1, + "*" + ], + [ + 59, + 15, + 0, + 39, + 0, + "*" + ], + [ + 69, + 47, + 0, + 48, + 0, + "MASK" + ], + [ + 78, + 39, + 0, + 51, + 0, + "*" + ], + [ + 79, + 32, + 0, + 51, + 1, + "*" + ], + [ + 80, + 47, + 0, + 51, + 2, + "*" + ], + [ + 81, + 51, + 0, + 24, + 1, + "MASK" + ], + [ + 82, + 4, + 0, + 52, + 0, + "*" + ], + [ + 83, + 52, + 0, + 24, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.7513148009015782, + "offset": { + "0": -256.5145409235732, + "1": -365.5770235061039 + } + } + }, + "version": 0.4 +} \ No newline at end of file diff --git a/workflows/subtitles_translate.json b/workflows/subtitles_translate.json new file mode 100644 index 0000000..209428b --- /dev/null +++ b/workflows/subtitles_translate.json @@ -0,0 +1,777 @@ +{ + "last_node_id": 41, + "last_link_id": 58, + "nodes": [ + { + "id": 11, + "type": "LayerStyle: DropShadow", + "pos": [ + 1364, + 274 + ], + "size": { + "0": 315, + "1": 266 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 46 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 28 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 20 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle: DropShadow" + }, + "widgets_values": [ + true, + "normal", + 75, + 8, + 8, + 2, + 8, + "#000000" + ] + }, + { + "id": 24, + "type": "LayerUtility: LaMa", + "pos": [ + 833, + 102 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 44 + }, + { + "name": "mask", + "type": "MASK", + "link": 56 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 45, + 46, + 47 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: LaMa" + }, + "widgets_values": [ + "lama", + "cuda", + false, + 25, + 6 + ] + }, + { + "id": 32, + "type": "ImageToMask", + "pos": [ + -25.47701164649243, + 402.810252108054 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 49 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 50, + 56 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageToMask" + }, + "widgets_values": [ + "green" + ] + }, + { + "id": 33, + "type": "MaskPreview+", + "pos": [ + 601, + 267 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 50 + } + ], + "properties": { + "Node name for S&R": "MaskPreview+" + } + }, + { + "id": 25, + "type": "PreviewImage", + "pos": [ + 1452, + -35 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 47 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 2, + "type": "PreviewImage", + "pos": [ + 1904, + 273 + ], + "size": { + "0": 714.4351806640625, + "1": 444.59918212890625 + }, + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 20 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 23, + "type": "Image OCR By Textract", + "pos": [ + -654, + 420 + ], + "size": { + "0": 210, + "1": 126 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 36 + } + ], + "outputs": [ + { + "name": "Text", + "type": "STRING", + "links": [ + 38 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "Left", + "type": "INT", + "links": [ + 39 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "Top", + "type": "INT", + "links": [ + 40 + ], + "shape": 3, + "slot_index": 2 + }, + { + "name": "Width", + "type": "INT", + "links": [ + 41 + ], + "shape": 3, + "slot_index": 3 + }, + { + "name": "Height", + "type": "INT", + "links": [ + 42 + ], + "shape": 3, + "slot_index": 4 + }, + { + "name": "Mask Image", + "type": "IMAGE", + "links": [ + 49 + ], + "shape": 3, + "slot_index": 5 + } + ], + "properties": { + "Node name for S&R": "Image OCR By Textract" + } + }, + { + "id": 15, + "type": "Bedrock - Claude", + "pos": [ + 305, + 811 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "prompt", + "type": "STRING", + "link": 30, + "widget": { + "name": "prompt" + } + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 29 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "Bedrock - Claude" + }, + "widgets_values": [ + "", + "anthropic.claude-3-haiku-20240307-v1:0", + 2048, + 1, + 1, + 71 + ] + }, + { + "id": 9, + "type": "LayerUtility: SimpleTextImage", + "pos": [ + 1316, + 619 + ], + "size": { + "0": 392.5400695800781, + "1": 406.2833557128906 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "size_as", + "type": "*", + "link": 45 + }, + { + "name": "text", + "type": "STRING", + "link": 29, + "widget": { + "name": "text" + } + }, + { + "name": "x_offset", + "type": "INT", + "link": 39, + "widget": { + "name": "x_offset" + } + }, + { + "name": "y_offset", + "type": "INT", + "link": 40, + "widget": { + "name": "y_offset" + } + }, + { + "name": "width", + "type": "INT", + "link": 41, + "widget": { + "name": "width" + } + }, + { + "name": "height", + "type": "INT", + "link": 42, + "widget": { + "name": "height" + } + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 28 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "mask", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: SimpleTextImage" + }, + "widgets_values": [ + "Are you going to Scarborough Fair?\nParsley, sage, rosemary, and thyme.\nRemember me to one who lives there,\nShe once was the true love of mine.", + "Alibaba-PuHuiTi-Heavy.ttf", + "left", + 80, + 8, + 50, + "#FFFFFF", + 0, + "#FF8000", + 0, + 300, + 512, + 512 + ] + }, + { + "id": 40, + "type": "String", + "pos": [ + -99.1735806485697, + 1134.5376124549446 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 58 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "String" + }, + "widgets_values": [ + "浪人" + ] + }, + { + "id": 39, + "type": "Compare", + "pos": [ + 306, + 1091 + ], + "size": { + "0": 315, + "1": 78 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "a", + "type": "*", + "link": 57 + }, + { + "name": "b", + "type": "*", + "link": 58 + } + ], + "outputs": [ + { + "name": "B", + "type": "BOOLEAN", + "links": null, + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "Compare" + }, + "widgets_values": [ + "a == b" + ] + }, + { + "id": 4, + "type": "LoadImage", + "pos": [ + -1091, + 346 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 36, + 44 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "textract_demo005.png", + "image" + ] + }, + { + "id": 17, + "type": "Prompt Template", + "pos": [ + -398, + 661 + ], + "size": { + "0": 521.4636840820312, + "1": 208.76742553710938 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "prompt", + "type": "STRING", + "link": 38, + "widget": { + "name": "prompt" + } + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 30, + 57 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "Prompt Template" + }, + "widgets_values": [ + "", + "翻译如下英文为日语\n[prompt]\n直接返回翻译后文本" + ] + } + ], + "links": [ + [ + 20, + 11, + 0, + 2, + 0, + "IMAGE" + ], + [ + 28, + 9, + 0, + 11, + 1, + "IMAGE" + ], + [ + 29, + 15, + 0, + 9, + 1, + "STRING" + ], + [ + 30, + 17, + 0, + 15, + 0, + "STRING" + ], + [ + 36, + 4, + 0, + 23, + 0, + "IMAGE" + ], + [ + 38, + 23, + 0, + 17, + 0, + "STRING" + ], + [ + 39, + 23, + 1, + 9, + 2, + "INT" + ], + [ + 40, + 23, + 2, + 9, + 3, + "INT" + ], + [ + 41, + 23, + 3, + 9, + 4, + "INT" + ], + [ + 42, + 23, + 4, + 9, + 5, + "INT" + ], + [ + 44, + 4, + 0, + 24, + 0, + "IMAGE" + ], + [ + 45, + 24, + 0, + 9, + 0, + "*" + ], + [ + 46, + 24, + 0, + 11, + 0, + "IMAGE" + ], + [ + 47, + 24, + 0, + 25, + 0, + "IMAGE" + ], + [ + 49, + 23, + 5, + 32, + 0, + "IMAGE" + ], + [ + 50, + 32, + 0, + 33, + 0, + "MASK" + ], + [ + 56, + 32, + 0, + 24, + 1, + "MASK" + ], + [ + 57, + 17, + 0, + 39, + 0, + "*" + ], + [ + 58, + 40, + 0, + 39, + 1, + "*" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 1.1000000000000005, + "offset": { + "0": 986.1125011031153, + "1": -372.55607836403607 + } + } + }, + "version": 0.4 +} \ No newline at end of file