Membrane.FFmpeg.Transcoder

Hex.pm Version

Membrane plugin to transcode video into different qualities using FFmpeg.

Requirements

Development and tests

The tool versions and FFmpeg codec build flags used for development are pinned in mise.toml. Building FFmpeg requires a compiler and the native development libraries for x264, FDK AAC, Opus, and zvbi. With those installed, run mise install and then:

mix deps.get
mix test

The integration tests also require ffprobe. They check FFmpeg's encoders and decoders before starting any pipelines and report missing capabilities directly. Some distribution builds of FFmpeg omit libfdk_aac or libzvbi; check the installed build with ffmpeg -hide_banner -encoders and ffmpeg -hide_banner -decoders.

Passing tests stay quiet. Logs, including FFmpeg commands and warnings, are captured and printed on failure. Use mix test --slowest 10 for per-test timings.

The audio/video fixture is three seconds long; it covers the full quality ladder without encoding the original 30-second clip on every run. The subtitle and SCTE-35 fixtures retain their complete timelines.

Usage

Process exit and recovery

The public bin sends its parent this notification when it observes FFmpeg exiting:

{:transcoder_exited,
 %{reason: {:signal, :sigkill, false}, input_eos_received?: false}}

The reason is :normal for exit zero, {:status, code} for a nonzero exit, or {:signal, signal, core_dump?} for a signal. Infrastructure failures can report another reason, such as :port_closed. The boolean records whether input EOS had reached the FFmpeg filter when it observed the exit; it does not establish success.

The bin still emits output EOS after its readers drain, including after nonzero exits. The notification and output EOS can reach the application in either order. Process exit does not establish downstream sink completion, recording durability, or valid/complete media. Applications should coordinate their sinks and apply their own drain deadline before replacing a graph.

Each bin represents one run: FFmpeg is never restarted internally. Applications own recovery through supervision. Internal reader/element failures still propagate as OTP failures. Monitor or supervise the graph too: no exit notification is guaranteed if the bin is being removed or has crashed.

Subtitle readers and FIFOs belong to their individual runs. They drain on process exit and are cleaned up on normal completion or graph teardown.

Basic Multi-Quality Transcoding

spec = [
  child(:source, %Membrane.File.Source{location: "input.ts"})
  |> child(:transcoder, Membrane.FFmpeg.Transcoder)
  |> via_out(:video, options: [resolution: {-2, 1080}, bitrate: 6_500_000, fps: 30])
  |> child(:hd_sink, %Membrane.File.Sink{location: "hd.h264"}),

  get_child(:transcoder)
  |> via_out(:video, options: [resolution: {-2, 720}, bitrate: 3_300_000, fps: 30])
  |> child(:sd_sink, %Membrane.File.Sink{location: "sd.h264"}),

  get_child(:transcoder)
  |> via_out(:audio, options: [bitrate: 128_000, sample_rate: 48_000, channels: 2])
  |> child(:audio_sink, %Membrane.File.Sink{location: "audio.aac"})
]

Stream Copy (No Re-encoding)

spec = [
  child(:source, %Membrane.File.Source{location: "input.ts"})
  |> child(:transcoder, Membrane.FFmpeg.Transcoder)
  |> via_out(:video, options: [copy: true])
  |> child(:video_sink, %Membrane.File.Sink{location: "video.h264"}),

  get_child(:transcoder)
  |> via_out(:audio, options: [copy: true])
  |> child(:audio_sink, %Membrane.File.Sink{location: "audio.aac"})
]

MP4 Output with Parsers

spec = [
  child(:source, %Membrane.File.Source{location: "input.ts"})
  |> child(:transcoder, Membrane.FFmpeg.Transcoder)
  |> via_out(:video, options: [resolution: {-2, 720}, bitrate: 3_300_000])
  |> child(:h264_parser, %Membrane.H264.Parser{output_stream_structure: :avc1})
  |> child(:mp4_muxer, %Membrane.MP4.Muxer.ISOM{fast_start: true})
  |> child(:sink, %Membrane.File.Sink{location: "output.mp4"})
]

Teletext Subtitle Extraction

spec = [
  child(:source, %Membrane.File.Source{location: "input_with_teletext.ts"})
  |> child(:transcoder, Membrane.FFmpeg.Transcoder)
  |> via_out(:text, options: [source: {:dvb_teletext, 777}])
  |> child(:subtitle_sink, %Membrane.Testing.Sink{})
]

Video Options

Audio Options

Features

Copyright 2024, KIM Keep In Mind GmbH Licensed under the Apache License, Version 2.0