mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-07-09 07:57:30 +08:00
[Partner Nodes] feat(bria): add Transparent option to Remove Video Background
Bria's /remove_background endpoint accepts background_color='Transparent' when paired with an alpha-capable container/codec; pass output_container_and_codec= 'webm_vp9' in that case (otherwise mp4_h264, unchanged). The returned WebM flows through ComfyUI's VIDEO type as a file reference, so Save Video with the default auto/auto settings stream-copies it byte-for-byte with the alpha plane intact (covered by new test_save_to_auto_preserves_vp9_alpha_via_stream_copy). BriaTransparentVideoBackground is kept as-is because it remains the supported path for the IMAGE+MASK workflow: Get Video Components currently cannot expose alpha for VP9 streams (PyAV's default vp9 decoder drops the side alpha layer), so the per-frame compositing workflow still needs the dedicated node that decodes via libvpx-vp9 itself.
This commit is contained in:
parent
00b633f368
commit
1abab1d81e
@ -251,12 +251,17 @@ class BriaRemoveVideoBackground(IO.ComfyNode):
|
||||
node_id="BriaRemoveVideoBackground",
|
||||
display_name="Bria Remove Video Background",
|
||||
category="partner/video/Bria",
|
||||
description="Remove the background from a video using Bria. ",
|
||||
description="Remove the background from a video using Bria. Pick a color to composite "
|
||||
"the foreground over a solid background (returns an MP4), or pick 'Transparent' to "
|
||||
"get a WebM/VP9 video with a real alpha channel. Connect Transparent output directly "
|
||||
"to Save Video to write the alpha-channel WebM to disk; for per-frame compositing "
|
||||
"(images + mask sockets), use 'Bria Remove Video Background (Transparent)' instead.",
|
||||
inputs=[
|
||||
IO.Video.Input("video"),
|
||||
IO.Combo.Input(
|
||||
"background_color",
|
||||
options=[
|
||||
"Transparent",
|
||||
"Black",
|
||||
"White",
|
||||
"Gray",
|
||||
@ -268,7 +273,11 @@ class BriaRemoveVideoBackground(IO.ComfyNode):
|
||||
"Magenta",
|
||||
"Orange",
|
||||
],
|
||||
tooltip="Background color for the output video.",
|
||||
tooltip="Background color for the output video. 'Transparent' returns a "
|
||||
"WebM/VP9 video with an alpha channel (best paired with Save Video); other "
|
||||
"options composite the foreground over the chosen solid color and return an MP4. "
|
||||
"For per-frame compositing (images + mask sockets) use 'Bria Remove Video "
|
||||
"Background (Transparent)' instead.",
|
||||
),
|
||||
IO.Int.Input(
|
||||
"seed",
|
||||
@ -301,13 +310,17 @@ class BriaRemoveVideoBackground(IO.ComfyNode):
|
||||
seed: int,
|
||||
) -> IO.NodeOutput:
|
||||
validate_video_duration(video, max_duration=60.0)
|
||||
# Bria returns 422 if background_color="Transparent" is paired with mp4_h264
|
||||
# (no alpha plane). VP9-in-WebM carries the alpha; downstream VIDEO handling
|
||||
# preserves it as long as Save Video uses auto/auto (stream-copy) settings.
|
||||
output_container_and_codec = "webm_vp9" if background_color == "Transparent" else "mp4_h264"
|
||||
response = await sync_op(
|
||||
cls,
|
||||
ApiEndpoint(path="/proxy/bria/v2/video/edit/remove_background", method="POST"),
|
||||
data=BriaRemoveVideoBackgroundRequest(
|
||||
video=await upload_video_to_comfyapi(cls, video),
|
||||
background_color=background_color,
|
||||
output_container_and_codec="mp4_h264",
|
||||
output_container_and_codec=output_container_and_codec,
|
||||
seed=seed,
|
||||
),
|
||||
response_model=BriaStatusResponse,
|
||||
@ -504,8 +517,9 @@ class BriaTransparentVideoBackground(IO.ComfyNode):
|
||||
display_name="Bria Remove Video Background (Transparent)",
|
||||
category="partner/video/Bria",
|
||||
description="Remove the background from a video using Bria and return the cut-out frames "
|
||||
"plus an alpha mask. Connect both to a compositing node, or feed them to Save WEBM to "
|
||||
"write a transparent video.",
|
||||
"plus an alpha mask, ready for per-frame compositing or feeding into Save WEBM. Use this "
|
||||
"when you need IMAGE + MASK sockets; if you only need to save a transparent video file, "
|
||||
"use 'Bria Remove Video Background' with background_color='Transparent' instead.",
|
||||
inputs=[
|
||||
IO.Video.Input("video"),
|
||||
IO.Int.Input(
|
||||
|
||||
@ -5,10 +5,13 @@ import os
|
||||
import av
|
||||
import io
|
||||
from fractions import Fraction
|
||||
import numpy as np
|
||||
|
||||
from comfy_api.input_impl.video_types import VideoFromFile, VideoFromComponents
|
||||
from comfy_api.util.video_types import VideoComponents
|
||||
from comfy_api.util.video_types import VideoComponents, VideoContainer, VideoCodec
|
||||
from comfy_api.input.basic_types import AudioInput
|
||||
from av.error import InvalidDataError
|
||||
from av.codec import CodecContext
|
||||
|
||||
EPSILON = 0.0001
|
||||
|
||||
@ -237,3 +240,63 @@ def test_duration_consistency(video_components):
|
||||
manual_duration = float(components.images.shape[0] / components.frame_rate)
|
||||
|
||||
assert duration == pytest.approx(manual_duration)
|
||||
|
||||
|
||||
def _make_webm_vp9_with_alpha(width=32, height=32, frames=5, fps=10) -> bytes:
|
||||
buf = io.BytesIO()
|
||||
with av.open(buf, mode="w", format="webm") as container:
|
||||
stream = container.add_stream("libvpx-vp9", rate=fps)
|
||||
stream.width = width
|
||||
stream.height = height
|
||||
stream.pix_fmt = "yuva420p"
|
||||
for i in range(frames):
|
||||
rgba = np.zeros((height, width, 4), dtype=np.uint8)
|
||||
rgba[..., 0] = 200
|
||||
rgba[..., 1] = 50 + i * 30
|
||||
rgba[..., 2] = 80
|
||||
rgba[..., 3] = (i + 1) * 50
|
||||
frame = av.VideoFrame.from_ndarray(rgba, format="rgba")
|
||||
frame = frame.reformat(format="yuva420p")
|
||||
for packet in stream.encode(frame):
|
||||
container.mux(packet)
|
||||
for packet in stream.encode(None):
|
||||
container.mux(packet)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _per_frame_alpha_means_libvpx(source) -> list:
|
||||
means = []
|
||||
with av.open(source, mode="r") as container:
|
||||
stream = container.streams.video[0]
|
||||
decoder = CodecContext.create("libvpx-vp9", "r")
|
||||
for packet in container.demux(stream):
|
||||
for frame in decoder.decode(packet):
|
||||
rgba = frame.to_ndarray(format="rgba")
|
||||
means.append(float(rgba[..., 3].mean()))
|
||||
return means
|
||||
|
||||
|
||||
def test_save_to_auto_preserves_vp9_alpha_via_stream_copy():
|
||||
"""Save Video with format=auto, codec=auto stream-copies a WebM/VP9+alpha file
|
||||
without touching the alpha plane. This is the assumption that the consolidated
|
||||
'Bria Remove Video Background' node relies on when background_color='Transparent'
|
||||
(which makes Bria return a webm_vp9 url with a real alpha channel)."""
|
||||
raw = _make_webm_vp9_with_alpha()
|
||||
original_means = _per_frame_alpha_means_libvpx(io.BytesIO(raw))
|
||||
assert len(original_means) > 0, "test fixture failed to produce frames"
|
||||
assert original_means == sorted(original_means), (
|
||||
"test fixture alpha did not encode as monotonically increasing"
|
||||
)
|
||||
|
||||
video = VideoFromFile(io.BytesIO(raw))
|
||||
out_buf = io.BytesIO()
|
||||
video.save_to(out_buf, format=VideoContainer.AUTO, codec=VideoCodec.AUTO)
|
||||
|
||||
out_buf.seek(0)
|
||||
saved_means = _per_frame_alpha_means_libvpx(out_buf)
|
||||
|
||||
assert len(saved_means) == len(original_means)
|
||||
for original, saved in zip(original_means, saved_means):
|
||||
assert original == pytest.approx(saved, abs=EPSILON), (
|
||||
f"alpha lost across stream-copy round-trip: {original} -> {saved}"
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user