Y4m (y4m v0.3.5)

Module for working with uncompressed frames of YCbCr videos.

Only a subset of color spaces are supported (C420, C444), all others (C420jpeg, C420paldv, C422, Cmono) are missing.

open-file-and-count-frames-in-file

Open file and count frames in file

Y4m.stream returns a tuple of metadata and a lazy stream.

iex> {props, stream} = Y4m.stream("test/videos/example.y4m")
iex> props
%{aspect_ratio: [0, 0], color_space: :C420, frame_rate: [25, 1], height: 288, interlacing: :progressive, width: 384}
# get number of frames
iex> stream |> Enum.to_list() |> length()
51

using-the-stream

Using the Stream

The metadata dictionary contains information about the resolution, color space and framerate as specified by the file header.

The stream is read directly from file. That means that the streams state is implicitly handled by the file handle. When taking a frame from the stream, the stream will continue with the next frame in the file. This may lead to confusion as a hidden shared state is involved. Each frame is a list of binaries.

iex> {_props, stream} = Y4m.stream("test/videos/example.y4m")
iex> [<<_y::binary>>, <<_u::binary>>, <<_v::binary>>] = Enum.at(stream, 0)
iex> # frames do not match because of shared state
iex> Enum.at(stream, 0) != Enum.at(stream, 0)
true

looping-a-file

Looping a file

Sometimes it's useful to loop a video in file. This can be done using GenServer to explicitly capture the frame streams state and to reopen the file as needed:

iex> defmodule Y4mLoop do
...>   use GenServer
...>
...>   def start_link(file_path), do: GenServer.start_link(__MODULE__, file_path)
...>   def next(loop), do: GenServer.call(loop, :next)
...>   def properties(loop), do: GenServer.call(loop, :properties)
...>
...>   @impl true
...>   def init(file_path) do
...>     {props, stream} = Y4m.stream(file_path)
...>     {:ok, {file_path, props, stream}}
...>   end
...>
...>   @impl true
...>   def handle_call(:next, _from, {file_path, props, stream}) do
...>     case stream |> Enum.take(1) do
...>       [frame] ->
...>         {:reply, frame, {file_path, props, stream}}
...>
...>       [] ->
...>         # reopen video file
...>         {props, stream} = Y4m.stream(file_path)
...>         [frame] = stream |> Enum.take(1)
...>         {:reply, frame, {file_path, props, stream}}
...>     end
...>   end
...>
...>   @impl true
...>   def handle_call(:properties, _from, {file_path, props, stream}) do
...>     {:reply, props, {file_path, props, stream}}
...>   end
...> end
...>
iex> # load file with fewer than 420 frames
iex> {:ok, loop} = Y4mLoop.start_link("test/videos/example.y4m")
iex> 1..420 |> Enum.map(fn _i -> Y4mLoop.next(loop) end)

Link to this section Summary

Functions

Same as stream/2

Reads y4m frames from a file on disk. Returns parsed file header and lazy stream across all frames in file.

Link to this section Functions

Link to this function

append(frames, writer)

@spec stream(binary() | pid()) ::
  {%{}, %Y4m.Stream{file: term(), props: term()}} | {:error, atom()}

Same as stream/2

Link to this function

stream(file, opts)

@spec stream(binary() | pid(), Y4m.Stream.options()) ::
  {%{}, Enum.t()} | {:error, atom()}

Reads y4m frames from a file on disk. Returns parsed file header and lazy stream across all frames in file.

examples

Examples

iex> {:ok, file} = File.open("test/videos/example.y4m")
iex> {_props, stream} = Y4m.stream(file)
iex> stream |> Enum.take(10)
Link to this function

write(file_path, props, frames \\ [])