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