Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic overlay update #4

Merged
merged 10 commits into from
Jul 24, 2024
4 changes: 4 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ workflows:
build:
jobs:
- elixir/build_test:
cache-version: 4
varsill marked this conversation as resolved.
Show resolved Hide resolved
filters: &filters
tags:
only: /v.*/
- elixir/test:
cache-version: 4
filters:
<<: *filters
- elixir/lint:
cache-version: 4
filters:
<<: *filters
- elixir/hex_publish:
WojciechBarczynski marked this conversation as resolved.
Show resolved Hide resolved
cache-version: 4
requires:
- elixir/build_test
- elixir/test
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The package can be installed by adding `membrane_overlay_plugin` to your list of
```elixir
def deps do
[
{:membrane_overlay_plugin, "~> 0.1.1"}
{:membrane_overlay_plugin, "~> 0.2.0"}
]
end
```
Expand Down
5 changes: 4 additions & 1 deletion example.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Mix.install([
defmodule Example do
import Membrane.ChildrenSpec
require Membrane.RCPipeline, as: RCPipeline
alias Membrane.OverlayFilter.OverlayDescription

def run() do
{:ok, overlay} =
Expand All @@ -30,7 +31,9 @@ defmodule Example do
})
|> child(Membrane.H264.Parser)
|> child(Membrane.H264.FFmpeg.Decoder)
|> child(%Membrane.OverlayFilter{overlay: overlay, x: :right, y: :top})
|> child(%Membrane.OverlayFilter{
initial_overlay: %OverlayDescription{overlay: overlay, x: :right, y: :top}
})
|> child(Membrane.H264.FFmpeg.Encoder)
|> child(:sink, %Membrane.File.Sink{location: "output.h264"})
)
Expand Down
84 changes: 46 additions & 38 deletions lib/membrane_overlay_filter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ defmodule Membrane.OverlayFilter do
Applies image or text overlay to video.

Based on `Image`.

You need to provide the first overlay description as the `initial_overlay` option.
To update overlay dynamically you can send {:update_overlay,`Membrane.OverlayFilter.OverlayDescription`}
notification from parent.
"""
use Membrane.Filter

require Membrane.Logger

alias Membrane.OverlayFilter.OverlayDescription
alias Membrane.RawVideo

def_input_pad :input, accepted_format: %RawVideo{pixel_format: :I420}
Expand All @@ -14,50 +21,16 @@ defmodule Membrane.OverlayFilter do
alias Vix.Vips.Image, as: Vimage
alias Vix.Vips.Operation

def_options overlay: [
spec: Path.t() | Vix.Vips.Image.t(),
description: """
Path to the overlay image or a `Vix` image.

You can get a `Vix` image for example by calling `Image.open/2`,
`Image.Text.text/2` or `Vix.Vips.Image.new_from_buffer/2`.
"""
],
x: [
spec: integer() | :center | :left | :right,
default: :center,
description: """
Distance of the overlay image from the left (or right if negative)
border of the frame. Can be also set to center, left or right.
"""
],
y: [
spec: integer() | :middle | :top | :bottom,
default: :middle,
def_options initial_overlay: [
spec: OverlayDescription.t(),
description: """
Distance of the overlay image from the top (or bottom if negative)
border of the frame. Can be also set to middle, top or bottom.
"""
],
blend_mode: [
spec: Image.BlendMode.t(),
default: :over,
description: """
The manner in which the overlay is composed on the frame.
Description of the overlay that will be initially used.
"""
]

@impl true
def handle_init(_ctx, options) do
opts_y = options |> Map.take([:x, :y, :blend_mode]) |> Enum.to_list()
uv_x = if is_integer(options.x), do: div(options.x, 2), else: options.x
uv_y = if is_integer(options.y), do: div(options.y, 2), else: options.y
opts_uv = [x: uv_x, y: uv_y, blend_mode: options.blend_mode]

state = %{
overlay_planes: open_overlay(options.overlay),
compose_options: {opts_y, opts_uv}
}
state = state_from_overlay_description(options.initial_overlay)

{[], state}
end
Expand All @@ -71,6 +44,41 @@ defmodule Membrane.OverlayFilter do
{[buffer: {:output, %{buffer | payload: composed}}], state}
end

@impl true
def handle_parent_notification(
{:update_overlay, overlay_description = %OverlayDescription{}},
_ctx,
_state
) do
state = state_from_overlay_description(overlay_description)

{[], state}
end

@impl true
def handle_parent_notification(other, _ctx, state) do
Membrane.Logger.warning("Unsupported parent notification: #{inspect(other)}")
{[], state}
end

@spec state_from_overlay_description(OverlayDescription.t()) :: map()
defp state_from_overlay_description(%OverlayDescription{
x: x,
y: y,
overlay: overlay,
blend_mode: blend_mode
}) do
opts_y = [x: x, y: y, blend_mode: blend_mode]
uv_x = if is_integer(x), do: div(x, 2), else: x
uv_y = if is_integer(y), do: div(y, 2), else: y
opts_uv = [x: uv_x, y: uv_y, blend_mode: blend_mode]

%{
overlay_planes: open_overlay(overlay),
compose_options: {opts_y, opts_uv}
}
end

defp open_overlay(overlay) do
overlay = if is_binary(overlay), do: Image.open!(overlay), else: overlay
{:ok, overlay_yuv} = Image.YUV.write_to_binary(overlay, :C420)
Expand Down
33 changes: 33 additions & 0 deletions lib/overlay_description.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule Membrane.OverlayFilter.OverlayDescription do
@moduledoc """
Specifies the overlay, its position on the underlay image
and the blend mode.
"""

@enforce_keys [:overlay]
defstruct @enforce_keys ++ [x: :center, y: :middle, blend_mode: :over]

@typedoc """
Specifies the overlay, its position on the underlay image
and the blend mode.
The following fields can be specified:
* `overlay` - Path to the overlay image or a `Vix` image.
You can get a `Vix` image for example by calling `Image.open/2`,
`Image.Text.text/2` or `Vix.Vips.Image.new_from_buffer/2`.
* `x` - Distance of the overlay image from the left (or right if negative)
border of the frame. Can be also set to center, left or right.
Defaults to `:center`.
* `y` - Distance of the overlay image from the top (or bottom if negative)
border of the frame. Can be also set to middle, top or bottom.
Defaults to `:middle`.
* `blend_mode` - The manner in which the overlay is composed on the frame.
Defaults to `:over`.

"""
@type t :: %__MODULE__{
overlay: Path.t() | Vix.Vips.Image.t(),
x: integer() | :center | :left | :right,
y: integer() | :middle | :top | :bottom,
blend_mode: Image.BlendMode.t()
}
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Membrane.Overlay.Plugin.Mixfile do
use Mix.Project

@version "0.1.1"
@version "0.2.0"
@github_url "https://github.com/membraneframework/membrane_overlay_plugin"

def project do
Expand Down
Loading