lib/ivf.ex

defmodule Ivf do
  @moduledoc """
  Collection of convenience functions to work with IVF files as described here: https://wiki.multimedia.cx/index.php/Duck_IVF.

  ## Reading Files
  ```elixir
  {props, stream} = Ivf.stream!("test/videos/test_vp8.ivf")
  %Ivf.Props{
    width: 320,
    height: 180,
    time_base: {1, 5},
    frame_count: 280,
    codec: "VP80"
  } = props
  200 = stream |> Enum.to_list |> length
  ```
  ____
  ____

  ## Writing Files
  ```elixir
  file = File.open!("test/videos/out.ivf", [:binary, :write])
  writer = Ivf.write!(file, Ivf.Props.new([]))
  # File will not be written to until frames are appended
  {:ok, %File.Stat{size: 0}} = File.stat("test/videos/out.ivf")
  # writer accepts any binary or list
  writer = Ivf.Writer.append(writer, "invalid_test_frame")
  # time stamps can also be provided
  writer = Ivf.Writer.append(writer, {1, "invalid_test_frame"})
  writer = Ivf.Writer.append(writer, {3, "invalid_test_frame"})
  # close the writer, the frame count in the header will be adjusted
  :ok = Ivf.Writer.close(writer)
  ```
  """

  @doc """
  Open an IVF file and lazy stream all frames.
  """
  @spec stream(binary) :: {:ok, Ivf.Props.t(), Stream.t()} | {:error, atom}
  def stream(file_path) when is_binary(file_path), do: Ivf.Stream.init(file_path)

  @doc """
  Open an IVF file and lazy stream all frames.
  """
  @spec stream!(binary) :: {Ivf.Props.t(), Stream.t()}
  def stream!(file_path) when is_binary(file_path) do
    case stream(file_path) do
      {:ok, props, stream} -> {props, stream}
      {:error, reason} -> raise "cannot stream file #{inspect(file_path)}, #{inspect(reason)}"
    end
  end

  @doc """
  Create a new IVF file.
  """
  @spec write!(binary, Ivf.Props.t()) :: Ivf.Writer.t()
  def write(file_path, props) when is_binary(file_path),
    do: Ivf.Writer.start_link(file_path, props)

  def write!(file_path, props) when is_binary(file_path) do
    case write(file_path, props) do
      {:ok, writer} -> writer
      {:error, reason} -> raise "cannot access file #{inspect(file_path)}, #{inspect(reason)}"
    end
  end
end