diff --git a/livebooks/video_compositor/res/input.h264 b/livebooks/video_compositor/res/input.h264 deleted file mode 100644 index aef03ffb..00000000 Binary files a/livebooks/video_compositor/res/input.h264 and /dev/null differ diff --git a/livebooks/video_compositor/video_compositor.livemd b/livebooks/video_compositor/video_compositor.livemd deleted file mode 100644 index c7dd5e99..00000000 --- a/livebooks/video_compositor/video_compositor.livemd +++ /dev/null @@ -1,183 +0,0 @@ -# Video Compositor Example - -```elixir -File.cd(__DIR__) -Logger.configure(level: :error) - -Mix.install([ - {:membrane_core, "~> 0.11.2"}, - {:membrane_file_plugin, "~> 0.13.0"}, - {:membrane_raw_video_format, "~> 0.2"}, - {:membrane_h264_ffmpeg_plugin, "~> 0.25.2"}, - {:membrane_video_compositor_plugin, "~> 0.2.1"}, - {:membrane_hackney_plugin, "~> 0.9.0"}, - {:membrane_realtimer_plugin, "~> 0.6.0"}, - {:membrane_kino_plugin, github: "membraneframework-labs/membrane_kino_plugin"} -]) -``` - -## Installation - -To run this demo one needs to install native dependencies: - -1. [H264 FFmpeg](https://github.com/membraneframework/membrane_h264_ffmpeg_plugin/#installation) -2. [Video Compositor](https://github.com/membraneframework/membrane_video_compositor_plugin#installation) - -## Pipeline definition - -Defines a video compositor with a `1440x960` output scene, waiting for up to five videos. Each of them is downloaded in real-time from the network using the `Hackney` module, parsed and decoded from the `H264` format, and composed in real-time. After that, they will be displayed using the `Kino.Video` player. - -```elixir -defmodule DynamicVideoComposition do - use Membrane.Pipeline - - alias Membrane.H264.FFmpeg.{Encoder, Parser, Decoder} - alias Membrane.{Pad, RawVideo, Time} - - alias Membrane.{ - Hackney, - Kino, - VideoCompositor - } - - alias Membrane.VideoCompositor.RustStructs.BaseVideoPlacement, as: VideoPlacement - alias Membrane.VideoCompositor.VideoTransformations - - alias Membrane.VideoCompositor.VideoTransformations.TextureTransformations.{ - CornersRounding, - Cropping - } - - @media_url "http://raw.githubusercontent.com/membraneframework/static/gh-pages/samples/big-buck-bunny/bun33s_720x480.h264" - - @framerate {25, 1} - - @output_video_format %RawVideo{ - width: 1440, - height: 960, - framerate: @framerate, - pixel_format: :I420, - aligned: true - } - - @placement %VideoPlacement{ - size: {720, 480}, - position: nil, - z_value: nil - } - - @positions [{0, 0}, {720, 0}, {0, 480}, {720, 480}, {360, 240}] - @z_values [0.0, 0.0, 0.0, 0.0, 1.0] - - @corners_rounding %CornersRounding{border_radius: 75} - @cropping %Cropping{ - crop_top_left_corner: {0.1, 0.1}, - crop_size: {0.8, 0.8} - } - - @transformations [ - %VideoTransformations{texture_transformations: [@corners_rounding]}, - %VideoTransformations{texture_transformations: [@corners_rounding]}, - %VideoTransformations{texture_transformations: [@corners_rounding]}, - %VideoTransformations{texture_transformations: [@corners_rounding]}, - %VideoTransformations{texture_transformations: [@cropping, @corners_rounding]} - ] - - @impl true - def handle_init(_ctx, options) do - structure = - child(:compositor, %VideoCompositor{stream_format: @output_video_format}) - # Kino player requires H264 frames without b-frames - |> child(:encoder, %Encoder{profile: :baseline}) - |> via_in(:video) - |> child(:player, %Kino.Player.Sink{kino: options[:kino]}) - - start_time = Time.vm_time() - state = %{next_video_id: 0, start_time: start_time} - - {[spec: structure, playback: :playing], state} - end - - @impl true - def handle_info(:add_video, _ctx, state) when state.next_video_id >= 5 do - {[], state} - end - - @impl true - def handle_info(:add_video, _ctx, state) do - %{start_time: start_time, next_video_id: next_video_id} = state - - position = Enum.at(@positions, next_video_id) - z_value = Enum.at(@z_values, next_video_id) - placement = %VideoPlacement{@placement | position: position, z_value: z_value} - - transformations = Enum.at(@transformations, next_video_id) - - offset = Time.vm_time() - start_time - - video_id = next_video_id - - video_source = - child({:media_source, video_id}, %Hackney.Source{ - location: @media_url, - hackney_opts: [follow_redirect: true], - max_retries: 3 - }) - |> child({:parser, video_id}, %Parser{framerate: @framerate}) - |> child({:decoder, video_id}, Decoder) - # Hackney source may fetch all frames at once from one source and flood compositor. - # This could cause undefined behaviour if the first source sent the entire stream - # and EOS signal before connecting the other sources. - # To ensure real-time processing, all streams are synchronized by reducing - # processing speed (based on the framerate). - |> child({:realtimer, video_id}, Membrane.Realtimer) - |> via_in(Pad.ref(:input, video_id), - options: [ - initial_placement: placement, - initial_video_transformations: transformations, - timestamp_offset: offset - ] - ) - |> get_child(:compositor) - - {[spec: video_source], %{state | next_video_id: next_video_id + 1}} - end -end - -:ok -``` - -## Playing video - -Activate pipeline and wait for videos: - -```elixir -kino = Membrane.Kino.Player.new(video: true) - -{:ok, _supervisor_pid, pipeline} = - Membrane.Pipeline.start(DynamicVideoComposition, kino: kino) - -kino -``` - -One can add up to five video inputs: - -```elixir -send(pipeline, :add_video) -``` - -```elixir -send(pipeline, :add_video) -``` - -```elixir -send(pipeline, :add_video) -``` - -```elixir -send(pipeline, :add_video) -``` - -```elixir -send(pipeline, :add_video) -```