defmodule Xav.Reader do
@moduledoc """
Media reader.
"""
@typedoc """
Reader options.
* `read` - determines which stream to read from a file with both audio and video.
Defaults to `:video`.
* `device?` - determines whether path points to video camera. Defaults to `false`.
"""
@type opts :: [read: :audio | :video, device?: boolean]
@type t() :: %__MODULE__{
reader: reference(),
in_format: atom(),
out_format: atom(),
sample_rate: integer() | nil,
bit_rate: integer(),
duration: integer(),
codec: atom()
}
@enforce_keys [:reader, :in_format, :out_format, :bit_rate, :duration, :codec]
defstruct @enforce_keys ++ [:sample_rate]
@doc """
The same as new/1 but raises on error.
"""
@spec new!(String.t(), opts) :: t()
def new!(path, opts) do
case new(path, opts) do
{:ok, reader} -> reader
{:error, reason} -> raise "Couldn't create a new reader. Reason: #{inspect(reason)}"
end
end
@doc """
Creates a new media reader.
Both reading from a file and video camera is supported.
In case of video camera, v4l2 driver is required and FPS are
locked to 10.
Microphone input is not supported.
"""
@spec new(String.t(), opts()) :: {:ok, t()} | {:error, term()}
def new(path, opts) do
read = opts[:read] || :video
device? = opts[:device?] || false
case Xav.NIF.new_reader(path, to_int(device?), to_int(read)) do
{:ok, reader, in_format, out_format, sample_rate, bit_rate, duration, codec} ->
{:ok,
%__MODULE__{
reader: reader,
in_format: in_format,
out_format: out_format,
sample_rate: sample_rate,
bit_rate: bit_rate,
duration: duration,
codec: to_human_readable(codec)
}}
{:ok, reader, in_format, out_format, bit_rate, duration, codec} ->
{:ok,
%__MODULE__{
reader: reader,
in_format: in_format,
out_format: out_format,
bit_rate: bit_rate,
duration: duration,
codec: to_human_readable(codec)
}}
{:error, _reason} = err ->
err
end
end
@doc """
Reads next frame.
Frame is always decoded. Video frames are always in RGB format.
"""
@spec next_frame(t()) :: {:ok, Xav.Frame.t()} | {:error, :eof}
def next_frame(%__MODULE__{reader: reader}) do
case Xav.NIF.next_frame(reader) do
{:ok, {data, format, width, height, pts}} ->
{:ok, Xav.Frame.new(data, format, width, height, pts)}
{:ok, {data, format, samples, pts}} ->
{:ok, Xav.Frame.new(data, format, samples, pts)}
{:error, :eof} = err ->
err
end
end
defp to_human_readable(:libdav1d), do: :av1
defp to_human_readable(:mp3float), do: :mp3
defp to_human_readable(other), do: other
defp to_int(:video), do: 1
defp to_int(:audio), do: 0
defp to_int(true), do: 1
defp to_int(false), do: 0
end