Skip to content

Commit

Permalink
Add dynamic overlay update (#4)
Browse files Browse the repository at this point in the history
* Add dynamic overlay update

* Add overlay description struct and use it in unified way for both the initial overlay and dynamic overlay changes

---------

Co-authored-by: Łukasz Kita <lukasz.kita0@gmail.com>
  • Loading branch information
WojciechBarczynski and varsill authored Jul 24, 2024
1 parent f5843d3 commit 878d43e
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 67 deletions.
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
filters: &filters
tags:
only: /v.*/
- elixir/test:
cache-version: 4
filters:
<<: *filters
- elixir/lint:
cache-version: 4
filters:
<<: *filters
- elixir/hex_publish:
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

0 comments on commit 878d43e

Please sign in to comment.