diff --git a/__init__.py b/__init__.py index 0a6e81d..7b6dc9d 100644 --- a/__init__.py +++ b/__init__.py @@ -26,7 +26,13 @@ from .nodes import ( ShotByTextManualPlacementNode, ShotByTextManualPaddingNode, ShotByTextCustomCoordinatesNode, - AttributionByImageNode + AttributionByImageNode, + RemoveVideoBackgroundNode, + VideoSolidColorBackgroundNode, + VideoMaskByPromptNode, + VideoMaskByKeyPointsNode, + VideoIncreaseResolutionNode, + VideoEraseElementsNode, ) # Map the node class to a name used internally by ComfyUI @@ -59,6 +65,12 @@ NODE_CLASS_MAPPINGS = { "AttributionByImageNode": AttributionByImageNode, "GenerateImageNodeV2": GenerateImageNodeV2, "RefineImageNodeV2": RefineImageNodeV2, + "RemoveVideoBackgroundNode":RemoveVideoBackgroundNode, + "VideoSolidColorBackgroundNode":VideoSolidColorBackgroundNode, + "VideoMaskByPromptNode":VideoMaskByPromptNode, + "VideoMaskByKeyPointsNode":VideoMaskByKeyPointsNode, + "VideoIncreaseResolutionNode":VideoIncreaseResolutionNode, + "VideoEraseElementsNode":VideoEraseElementsNode, } # Map the node display name to the one shown in the ComfyUI node interface NODE_DISPLAY_NAME_MAPPINGS = { @@ -90,4 +102,10 @@ NODE_DISPLAY_NAME_MAPPINGS = { "AttributionByImageNode": "Attribution By Image Node", "GenerateImageNodeV2": "Generate Image", "RefineImageNodeV2": "Refine and Regenerate Image", + "RemoveVideoBackgroundNode": "Bria Remove Video Background", + "VideoSolidColorBackgroundNode":"Bria SolidColor Background Video", + "VideoMaskByPromptNode":"Bria Video Mask By Prompt", + "VideoMaskByKeyPointsNode":"Bria Video Mask By Key Points", + "VideoIncreaseResolutionNode":"Bria Video Increase Resolution", + "VideoEraseElementsNode":"Bria Video Erase Elements", } diff --git a/nodes/__init__.py b/nodes/__init__.py index 5350502..d20292a 100644 --- a/nodes/__init__.py +++ b/nodes/__init__.py @@ -28,3 +28,9 @@ from .shot_by_image_node import ShotByImageOriginalNode from .shot_by_image_manual_placement_node import ShotByImageManualPlacementNode from .shot_by_image_manual_padding_node import ShotByImageManualPaddingNode from .attribution_by_image_node import AttributionByImageNode +from .video_nodes.remove_video_background_node import RemoveVideoBackgroundNode +from .video_nodes.video_increase_resolution_node import VideoIncreaseResolutionNode +from .video_nodes.video_solid_color_background_node import VideoSolidColorBackgroundNode +from .video_nodes.video_erase_elements_node import VideoEraseElementsNode +from .video_nodes.video_mask_by_prompt_node import VideoMaskByPromptNode +from .video_nodes.video_mask_by_key_points_node import VideoMaskByKeyPointsNode diff --git a/nodes/video_nodes/remove_video_background_node.py b/nodes/video_nodes/remove_video_background_node.py new file mode 100644 index 0000000..1f7519d --- /dev/null +++ b/nodes/video_nodes/remove_video_background_node.py @@ -0,0 +1,97 @@ +import requests +from ..common import deserialize_and_get_comfy_key, poll_status_until_completed + +class RemoveVideoBackgroundNode(): + """ + Bria Remove Video Background Node + + This node removes the background from videos using the Bria API. + It accepts a publicly accessible video URL and returns a processed video URL + with the background removed. + + Supported input resolution: up to 16000x16000 (16K) + + Parameters: + - video_url: Publicly accessible URL of the input video + - api_key: Your Bria API token + - output_container_and_codec: Output video format and codec (default: webm_vp9) + """ + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "video_url": ("STRING", {"default": ""}), + "api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), + }, + "optional": { + "preserve_audio": ("BOOLEAN", {"default":True}), + "output_container_and_codec": ([ + "mp4_h264", + "mp4_h265", + "webm_vp9", + "mov_h265", + "mov_proresks", + "mkv_h264", + "mkv_h265", + "mkv_vp9", + "gif" + ], {"default": "webm_vp9"}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("video_url_response",) + CATEGORY = "API Nodes" + FUNCTION = "execute" + + def __init__(self): + self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/remove_background" # Video RMBG API URL + + # Define the execute method as expected by ComfyUI + def execute(self, video_url, api_key, preserve_audio, output_container_and_codec="webm_vp9"): + if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN": + raise Exception("Please insert a valid API key.") + api_key = deserialize_and_get_comfy_key(api_key) + + # Prepare the API request payload + payload = { + "video": video_url, + "preserve_audio": preserve_audio, + "output_container_and_codec": output_container_and_codec + } + + headers = { + "Content-Type": "application/json", + "api_token": f"{api_key}" + } + + try: + response = requests.post(self.api_url, json=payload, headers=headers) + + if response.status_code == 200 or response.status_code == 202: + print('Initial Video RMBG request successful, polling for completion...') + response_dict = response.json() + + status_url = response_dict.get('status_url') + request_id = response_dict.get('request_id') + + if not status_url: + raise Exception("No status_url returned from API") + + print(f"Request ID: {request_id}, Status URL: {status_url}") + + final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5) + + result_video_url = final_response['result']['video_url'] + + print(f"Video processing completed. Result URL: {result_video_url}") + + + + return (result_video_url,) + else: + raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}") + + except Exception as e: + raise Exception(f"{e}") + diff --git a/nodes/video_nodes/video_erase_elements_node.py b/nodes/video_nodes/video_erase_elements_node.py new file mode 100644 index 0000000..4a32dd4 --- /dev/null +++ b/nodes/video_nodes/video_erase_elements_node.py @@ -0,0 +1,97 @@ +import requests +from ..common import deserialize_and_get_comfy_key, poll_status_until_completed + +class VideoEraseElementsNode(): + """ + Bria Video Erase Elements Node + + This node erases specific elements from videos based on a text prompt using the Bria API. + It accepts a publicly accessible video URL and a text instruction describing the object + to be masked and removed. + + Parameters: + - video_url: Publicly accessible URL of the input video + - api_key: Your Bria API token + - prompt: Text instruction describing the object to be masked + - output_container_and_codec: Output video format and codec (default: mp4_h264) + - preserve_audio: Audio preservation (default: True) + """ + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "video_url": ("STRING", {"default": ""}), + "mask_url": ("STRING", {"default": ""}), + "prompt": ("STRING", {"default": ""}), + "api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), + }, + "optional": { + "output_container_and_codec": ([ + "mp4_h264", + "mp4_h265", + "webm_vp9", + "mov_h265", + "mov_proresks", + "mkv_h264", + "mkv_h265", + "mkv_vp9", + "gif" + ], {"default": "mp4_h264"}), + "preserve_audio": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("video_url_response",) + CATEGORY = "API Nodes" + FUNCTION = "execute" + + def __init__(self): + self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/erase" + + def execute(self, video_url, mask_url, prompt, api_key, output_container_and_codec="mp4_h264", preserve_audio=True): + if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN": + raise Exception("Please insert a valid API key.") + api_key = deserialize_and_get_comfy_key(api_key) + + # Prepare the API request payload + payload = { + "video": video_url, + "mask":mask_url, + "prompt": prompt, + "output_container_and_codec": output_container_and_codec, + "preserve_audio": preserve_audio + } + + headers = { + "Content-Type": "application/json", + "api_token": f"{api_key}" + } + + try: + response = requests.post(self.api_url, json=payload, headers=headers) + + if response.status_code == 200 or response.status_code == 202: + print('Initial Video Erase Elements request successful, polling for completion...') + response_dict = response.json() + + status_url = response_dict.get('status_url') + request_id = response_dict.get('request_id') + + if not status_url: + raise Exception("No status_url returned from API") + + print(f"Request ID: {request_id}, Status URL: {status_url}") + + final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5) + + result_video_url = final_response['result']['video_url'] + + print(f"Video processing completed. Result URL: {result_video_url}") + + return (result_video_url,) + else: + raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}") + + except Exception as e: + raise Exception(f"{e}") \ No newline at end of file diff --git a/nodes/video_nodes/video_increase_resolution_node.py b/nodes/video_nodes/video_increase_resolution_node.py new file mode 100644 index 0000000..d02cbfb --- /dev/null +++ b/nodes/video_nodes/video_increase_resolution_node.py @@ -0,0 +1,95 @@ +import requests +from ..common import deserialize_and_get_comfy_key, poll_status_until_completed + +class VideoIncreaseResolutionNode(): + """ + Bria Video Increase Resolution Node + + This node increases the resolution of videos using the Bria API. + It accepts a publicly accessible video URL and returns a processed video URL + with increased resolution. + + Parameters: + - video_url: Publicly accessible URL of the input video + - api_key: Your Bria API token + - desired_increase: Integer scale factor for upscaling (2 or 4) + - output_container_and_codec: Output video format and codec (default: mp4_h264) + - preserve_audio: Audio preservation (default: True) + """ + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "video_url": ("STRING", {"default": ""}), + "api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), + }, + "optional": { + "desired_increase": (['2', '4'], {"default": '2'}), + "output_container_and_codec": ([ + "mp4_h264", + "mp4_h265", + "webm_vp9", + "mov_h265", + "mov_proresks", + "mkv_h264", + "mkv_h265", + "mkv_vp9", + "gif" + ], {"default": "mp4_h264"}), + "preserve_audio": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("video_url_response",) + CATEGORY = "API Nodes" + FUNCTION = "execute" + + def __init__(self): + self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/increase_resolution" + + def execute(self, video_url, api_key, desired_increase, output_container_and_codec="mp4_h264", preserve_audio=True): + if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN": + raise Exception("Please insert a valid API key.") + api_key = deserialize_and_get_comfy_key(api_key) + + # Prepare the API request payload + payload = { + "video": video_url, + "desired_increase": desired_increase, + "output_container_and_codec": output_container_and_codec, + "preserve_audio": preserve_audio + } + + headers = { + "Content-Type": "application/json", + "api_token": f"{api_key}" + } + + try: + response = requests.post(self.api_url, json=payload, headers=headers) + + if response.status_code == 200 or response.status_code == 202: + print('Initial Video Increase Resolution request successful, polling for completion...') + response_dict = response.json() + + status_url = response_dict.get('status_url') + request_id = response_dict.get('request_id') + + if not status_url: + raise Exception("No status_url returned from API") + + print(f"Request ID: {request_id}, Status URL: {status_url}") + + final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5) + + result_video_url = final_response['result']['video_url'] + + print(f"Video processing completed. Result URL: {result_video_url}") + + return (result_video_url,) + else: + raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}") + + except Exception as e: + raise Exception(f"{e}") \ No newline at end of file diff --git a/nodes/video_nodes/video_mask_by_key_points_node.py b/nodes/video_nodes/video_mask_by_key_points_node.py new file mode 100644 index 0000000..7ecf46f --- /dev/null +++ b/nodes/video_nodes/video_mask_by_key_points_node.py @@ -0,0 +1,101 @@ +import requests +from ..common import deserialize_and_get_comfy_key, poll_status_until_completed +import json + +class VideoMaskByKeyPointsNode(): + """ + Bria Video Mask by Key Points Node + + This node generates a mask for specific elements in videos based on key point coordinates + using the Bria API. It accepts a publicly accessible video URL and an array of coordinate + objects defining the mask hints. + + Parameters: + - video_url: Publicly accessible URL of the input video + - api_key: Your Bria API token + - key_points: Array of coordinate objects defining the mask hints (JSON format) + - output_container_and_codec: Output video format and codec (default: mp4_h264) + - preserve_audio: Audio preservation (default: True) + """ + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "video_url": ("STRING", {"default": ""}), + "key_points": ("STRING", {"default": "[]", "multiline": True}), + "api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), + }, + "optional": { + "output_container_and_codec": ([ + "mp4_h264", + "mp4_h265", + "webm_vp9", + "mov_h265", + "mov_proresks", + "mkv_h264", + "mkv_h265", + "mkv_vp9", + "gif" + ], {"default": "mp4_h264"}), + "preserve_audio": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("mask_url_response",) + CATEGORY = "API Nodes" + FUNCTION = "execute" + + def __init__(self): + self.api_url = "https://engine.prod.bria-api.com/v2/video/segment/mask_by_key_points" + + def execute(self, video_url, key_points, api_key, output_container_and_codec="mp4_h264", preserve_audio=True): + if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN": + raise Exception("Please insert a valid API key.") + api_key = deserialize_and_get_comfy_key(api_key) + + try: + key_points_array = json.loads(key_points) + except json.JSONDecodeError as e: + raise Exception(f"Invalid JSON format for key_points: {e}") + + # Prepare the API request payload + payload = { + "video": video_url, + "key_points": key_points_array, + "output_container_and_codec": output_container_and_codec, + "preserve_audio": preserve_audio + } + + headers = { + "Content-Type": "application/json", + "api_token": f"{api_key}" + } + + try: + response = requests.post(self.api_url, json=payload, headers=headers) + + if response.status_code == 200 or response.status_code == 202: + print('Initial Video Mask by Key Points request successful, polling for completion...') + response_dict = response.json() + + status_url = response_dict.get('status_url') + request_id = response_dict.get('request_id') + + if not status_url: + raise Exception("No status_url returned from API") + + print(f"Request ID: {request_id}, Status URL: {status_url}") + + final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5) + + result_mask_url = final_response['result']['mask_url'] + + print(f"Video mask processing completed. Result URL: {result_mask_url}") + + return (result_mask_url,) + else: + raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}") + + except Exception as e: + raise Exception(f"{e}") \ No newline at end of file diff --git a/nodes/video_nodes/video_mask_by_prompt_node.py b/nodes/video_nodes/video_mask_by_prompt_node.py new file mode 100644 index 0000000..ef43333 --- /dev/null +++ b/nodes/video_nodes/video_mask_by_prompt_node.py @@ -0,0 +1,95 @@ +import requests +from ..common import deserialize_and_get_comfy_key, poll_status_until_completed + +class VideoMaskByPromptNode(): + """ + Bria Video Mask by Prompt Node + + This node generates a mask for specific elements in videos based on a text prompt + using the Bria API. It accepts a publicly accessible video URL and a text instruction + describing the object to be masked. + + Parameters: + - video_url: Publicly accessible URL of the input video + - api_key: Your Bria API token + - prompt: Text instruction describing the object to be masked + - output_container_and_codec: Output video format and codec (default: mp4_h264) + - preserve_audio: Audio preservation (default: True) + """ + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "video_url": ("STRING", {"default": ""}), + "prompt": ("STRING", {"default": ""}), + "api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), + }, + "optional": { + "output_container_and_codec": ([ + "mp4_h264", + "mp4_h265", + "webm_vp9", + "mov_h265", + "mov_proresks", + "mkv_h264", + "mkv_h265", + "mkv_vp9", + "gif" + ], {"default": "mp4_h264"}), + "preserve_audio": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("mask_url_response",) + CATEGORY = "API Nodes" + FUNCTION = "execute" + + def __init__(self): + self.api_url = "https://engine.prod.bria-api.com/v2/video/segment/mask_by_prompt" + + def execute(self, video_url, prompt, api_key, output_container_and_codec="mp4_h264", preserve_audio=True): + if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN": + raise Exception("Please insert a valid API key.") + api_key = deserialize_and_get_comfy_key(api_key) + + # Prepare the API request payload + payload = { + "video": video_url, + "prompt": prompt, + "output_container_and_codec": output_container_and_codec, + "preserve_audio": preserve_audio + } + + headers = { + "Content-Type": "application/json", + "api_token": f"{api_key}" + } + + try: + response = requests.post(self.api_url, json=payload, headers=headers) + + if response.status_code == 200 or response.status_code == 202: + print('Initial Video Mask by Prompt request successful, polling for completion...') + response_dict = response.json() + + status_url = response_dict.get('status_url') + request_id = response_dict.get('request_id') + + if not status_url: + raise Exception("No status_url returned from API") + + print(f"Request ID: {request_id}, Status URL: {status_url}") + + final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5) + + result_mask_url = final_response['result']['mask_url'] + + print(f"Video mask processing completed. Result URL: {result_mask_url}") + + return (result_mask_url,) + else: + raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}") + + except Exception as e: + raise Exception(f"{e}") \ No newline at end of file diff --git a/nodes/video_nodes/video_solid_color_background_node.py b/nodes/video_nodes/video_solid_color_background_node.py new file mode 100644 index 0000000..b38d13d --- /dev/null +++ b/nodes/video_nodes/video_solid_color_background_node.py @@ -0,0 +1,107 @@ +import requests +from ..common import deserialize_and_get_comfy_key, poll_status_until_completed + +class VideoSolidColorBackgroundNode(): + """ + Bria Video Solid Color Background Node + + This node removes the background from videos and replaces it with a solid color + using the Bria API. It accepts a publicly accessible video URL and returns a + processed video URL with the specified background color. + + Parameters: + - video_url: Publicly accessible URL of the input video + - api_key: Your Bria API token + - background_color: Predefined color string (default: Transparent) + - output_container_and_codec: Output video format and codec (default: mp4_h264) + - preserve_audio: Audio preservation (default: True) + """ + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "video_url": ("STRING", {"default": ""}), + "api_key": ("STRING", {"default": "BRIA_API_TOKEN"}), + }, + "optional": { + "background_color": ([ + "Transparent", + "Black", + "White", + "Gray", + "Red", + "Green", + "Blue", + "Yellow", + "Cyan", + "Magenta", + "Orange" + ], {"default": "Transparent"}), + "output_container_and_codec": ([ + "mp4_h264", + "mp4_h265", + "webm_vp9", + "mov_h265", + "mov_proresks", + "mkv_h264", + "mkv_h265", + "mkv_vp9", + "gif" + ], {"default": "mp4_h264"}), + "preserve_audio": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("video_url_response",) + CATEGORY = "API Nodes" + FUNCTION = "execute" + + def __init__(self): + self.api_url = "https://engine.prod.bria-api.com/v2/video/edit/remove_background" + + def execute(self, video_url, api_key, background_color="Transparent", output_container_and_codec="mp4_h264", preserve_audio=True): + if api_key.strip() == "" or api_key.strip() == "BRIA_API_TOKEN": + raise Exception("Please insert a valid API key.") + api_key = deserialize_and_get_comfy_key(api_key) + + # Prepare the API request payload + payload = { + "video": video_url, + "background_color": background_color, + "output_container_and_codec": output_container_and_codec, + "preserve_audio": preserve_audio + } + + headers = { + "Content-Type": "application/json", + "api_token": f"{api_key}" + } + + try: + response = requests.post(self.api_url, json=payload, headers=headers) + + if response.status_code == 200 or response.status_code == 202: + print('Initial Video Solid Color Background request successful, polling for completion...') + response_dict = response.json() + + status_url = response_dict.get('status_url') + request_id = response_dict.get('request_id') + + if not status_url: + raise Exception("No status_url returned from API") + + print(f"Request ID: {request_id}, Status URL: {status_url}") + + final_response = poll_status_until_completed(status_url, api_key, timeout=3600, check_interval=5) + + result_video_url = final_response['result']['video_url'] + + print(f"Video processing completed. Result URL: {result_video_url}") + + return (result_video_url,) + else: + raise Exception(f"Error: API request failed with status code {response.status_code} {response.text}") + + except Exception as e: + raise Exception(f"{e}") \ No newline at end of file