Skip to content

Commit

Permalink
Refactor API
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 committed Jan 20, 2024
1 parent da06054 commit b1ce9aa
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 91 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,25 @@ end
Read from a file:

```elixir
r = Xav.new_reader!("./some_mp4_file.mp4")
{:ok, %Xav.Frame{} = frame} = Xav.next_frame(r)
r = Xav.Reader.new!("./some_mp4_file.mp4")
{:ok, %Xav.Frame{} = frame} = Xav.Reader.next_frame(r)
tensor = Xav.Frame.to_nx(frame)
Kino.Image.new(tensor)
```

Read from a camera:

```elixir
r = Xav.new_reader!("/dev/video0", device?: true)
{:ok, %Xav.Frame{} = frame} = Xav.next_frame(r)
r = Xav.Reader.new!("/dev/video0", device?: true)
{:ok, %Xav.Frame{} = frame} = Xav.Reader.next_frame(r)
tensor = Xav.Frame.to_nx(frame)
Kino.Image.new(tensor)
```

Speech to text:

```elixir
r = Xav.new_reader!("../sample.mp3", read: :audio)
r = Xav.Reader.new!("../sample.mp3", read: :audio)

{:ok, whisper} = Bumblebee.load_model({:hf, "openai/whisper-tiny"})
{:ok, featurizer} = Bumblebee.load_featurizer({:hf, "openai/whisper-tiny"})
Expand All @@ -57,8 +57,8 @@ serving =
# read a couple of frames
frames =
for _i <- 0..200 do
{:ok, frame} = Xav.next_frame(r)
Xav.to_nx(frame)
{:ok, frame} = Xav.Reader.next_frame(r)
Xav.Frame.to_nx(frame)
end

batch = Nx.Batch.concatenate(frames)
Expand Down
12 changes: 12 additions & 0 deletions lib/decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,27 @@ defmodule Xav.Decoder do
Audio/video decoder.
"""

@typedoc """
Supported codecs.
"""
@type codec() :: :opus | :vp8

@type t() :: reference()

@doc """
Creates a new decoder.
"""
@spec new(codec()) :: t()
def new(codec) do
Xav.NIF.new_decoder(codec)
end

@doc """
Decodes an audio or video frame.
Video frames are always in the RGB format.
Audio samples are always interleaved.
"""
@spec decode(t(), binary(), integer(), integer()) :: {:ok, Xav.Frame.t()} | {:error, atom()}
def decode(decoder, data, pts, dts) do
case Xav.NIF.decode(decoder, data, pts, dts) do
Expand Down
24 changes: 13 additions & 11 deletions lib/reader.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
defmodule Xav.Reader do
@moduledoc """
Media reader.
Audio/video files reader.
"""

@typedoc """
Reader options.
* `read` - determines which stream to read from a file with both audio and video.
* `read` - determines which stream to read from a file.
Defaults to `:video`.
* `device?` - determines whether path points to video camera. Defaults to `false`.
* `device?` - determines whether path points to the camera. Defaults to `false`.
"""
@type opts :: [read: :audio | :video, device?: boolean]

Expand All @@ -28,25 +28,25 @@ defmodule Xav.Reader do
@doc """
The same as new/1 but raises on error.
"""
@spec new!(String.t(), opts) :: t()
def new!(path, opts) do
@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.
Creates a new audio/video reader.
Both reading from a file and video camera is supported.
In case of video camera, v4l2 driver is required and FPS are
Both reading from a file and from a video camera are supported.
In case of using a video camera, the 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
def new(path, opts \\ []) do
read = opts[:read] || :video
device? = opts[:device?] || false

Expand Down Expand Up @@ -80,9 +80,11 @@ defmodule Xav.Reader do
end

@doc """
Reads next frame.
Reads the next frame.
Frame is always decoded. Video frames are always in RGB format.
A frame is always decoded.
Video frames are always in the RGB format.
Audio samples are always interleaved.
"""
@spec next_frame(t()) :: {:ok, Xav.Frame.t()} | {:error, :eof}
def next_frame(%__MODULE__{reader: reader}) do
Expand Down
20 changes: 0 additions & 20 deletions lib/xav.ex

This file was deleted.

53 changes: 53 additions & 0 deletions test/reader_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule Xav.ReaderTest do
use ExUnit.Case, async: true

test "new/1" do
assert {:ok, %Xav.Reader{}} = Xav.Reader.new("./test/fixtures/sample_h264.mp4")
assert {:error, _reason} = Xav.Reader.new("non_existing_input")
end

test "new!/1" do
%Xav.Reader{} = Xav.Reader.new!("./test/fixtures/sample_h264.mp4")
assert_raise RuntimeError, fn -> Xav.Reader.new!("non_existing_input") end
end

test "next_frame/1" do
{:ok, r} = Xav.Reader.new("./test/fixtures/sample_h264.mp4")
# test reading 5 seconds
for _i <- 0..(30 * 5), do: assert({:ok, %Xav.Frame{}} = Xav.Reader.next_frame(r))
end

# @tag :debug
# test "next_frame/1 audio" do
# {:ok, r} = Xav.Reader.new("./test/fixtures/sample.mp3", read: :audio)

# for _i <- 0..5 do
# assert({:ok, %Xav.Frame{} = frame} = Xav.Reader.next_frame(r))
# IO.inspect({byte_size(frame.data), frame})
# end
# end

test "to_nx/1" do
{:ok, r} = Xav.Reader.new("./test/fixtures/sample_h264.mp4")
{:ok, frame} = Xav.Reader.next_frame(r)
%Nx.Tensor{} = Xav.Frame.to_nx(frame)
end

test "eof" do
{:ok, r} = Xav.Reader.new("./test/fixtures/one_frame.mp4")
{:ok, _frame} = Xav.Reader.next_frame(r)
{:error, :eof} = Xav.Reader.next_frame(r)
end

@formats [{"h264", "h264"}, {"h264", "mkv"}, {"vp8", "webm"}, {"vp9", "webm"}, {"av1", "mkv"}]
Enum.map(@formats, fn {codec, container} ->
name = "#{codec} #{container}"
file = "./test/fixtures/sample_#{codec}.#{container}"

test name do
{:ok, r} = Xav.Reader.new(unquote(file))
# test reading 5 seconds
for _i <- 0..(30 * 5), do: assert({:ok, %Xav.Frame{}} = Xav.Reader.next_frame(r))
end
end)
end
53 changes: 0 additions & 53 deletions test/xav_test.exs

This file was deleted.

0 comments on commit b1ce9aa

Please sign in to comment.