elixir api
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
defmodule Chromaprint do
|
||||
@moduledoc """
|
||||
Elixir bindings for the [Chromaprint](https://github.com/acoustid/chromaprint)
|
||||
audio fingerprinting library.
|
||||
|
||||
Two ways to use it:
|
||||
|
||||
* **Streaming** - `new/1` → `start/2` → `feed/2` (any number of times) →
|
||||
`finish/1` → `fingerprint/1` (or `raw_fingerprint/1`, `hash/1`).
|
||||
* **One-shot** - `compute/2` takes the entire 16-bit PCM buffer and returns
|
||||
`{:ok, fingerprint_string}`.
|
||||
|
||||
Audio input is **little-endian signed 16-bit PCM**, interleaved across
|
||||
channels.
|
||||
"""
|
||||
|
||||
alias Chromaprint.NIF
|
||||
|
||||
@algorithms %{test1: 0, test2: 1, test3: 2, test4: 3, test5: 4}
|
||||
@algorithm_codes Map.new(@algorithms, fn {k, v} -> {v, k} end)
|
||||
@default_algorithm :test2
|
||||
|
||||
defmodule Context do
|
||||
@moduledoc """
|
||||
Opaque handle to a Chromaprint fingerprinting session.
|
||||
"""
|
||||
@enforce_keys [:ref, :algorithm]
|
||||
defstruct [:ref, :algorithm]
|
||||
|
||||
@type t :: %__MODULE__{ref: reference(), algorithm: atom()}
|
||||
end
|
||||
|
||||
@type algorithm :: :test1 | :test2 | :test3 | :test4 | :test5
|
||||
@type raw_fingerprint :: [non_neg_integer()]
|
||||
|
||||
@doc "Names of the supported algorithms."
|
||||
@spec algorithms() :: [algorithm()]
|
||||
def algorithms, do: Map.keys(@algorithms)
|
||||
|
||||
@doc "The version string of the linked chromaprint library."
|
||||
@spec version() :: String.t()
|
||||
def version, do: NIF.version()
|
||||
|
||||
@doc """
|
||||
Create a new fingerprinting context.
|
||||
|
||||
## Options
|
||||
* `:algorithm` - one of `#{inspect(Map.keys(@algorithms))}`. Defaults to
|
||||
`#{inspect(@default_algorithm)}` (chromaprint's `DEFAULT`).
|
||||
"""
|
||||
@spec new(keyword()) :: {:ok, Context.t()} | {:error, term()}
|
||||
def new(opts \\ []) do
|
||||
algorithm = Keyword.get(opts, :algorithm, @default_algorithm)
|
||||
|
||||
with {:ok, code} <- algorithm_to_code(algorithm),
|
||||
{:ok, ref} <- NIF.new_context(code) do
|
||||
{:ok, %Context{ref: ref, algorithm: algorithm}}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Start a stream. Must be called before any `feed/2`.
|
||||
|
||||
## Options
|
||||
* `:sample_rate` - required, e.g. `44_100`.
|
||||
* `:channels` - required, `1` or `2`.
|
||||
"""
|
||||
@spec start(Context.t(), keyword()) :: :ok | {:error, term()}
|
||||
def start(%Context{ref: ref}, opts) do
|
||||
sample_rate = Keyword.fetch!(opts, :sample_rate)
|
||||
channels = Keyword.fetch!(opts, :channels)
|
||||
NIF.start(ref, sample_rate, channels)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Feed PCM samples. `samples` is a binary (or iolist) of interleaved,
|
||||
little-endian signed 16-bit integers.
|
||||
"""
|
||||
@spec feed(Context.t(), iodata()) :: :ok | {:error, term()}
|
||||
def feed(%Context{ref: ref}, samples) do
|
||||
bin = IO.iodata_to_binary(samples)
|
||||
NIF.feed(ref, bin)
|
||||
end
|
||||
|
||||
@doc "Signal end of input. Must precede `fingerprint/1` / `raw_fingerprint/1`."
|
||||
@spec finish(Context.t()) :: :ok | {:error, term()}
|
||||
def finish(%Context{ref: ref}), do: NIF.finish(ref)
|
||||
|
||||
@doc "Get the compressed base64-style fingerprint string."
|
||||
@spec fingerprint(Context.t()) :: {:ok, String.t()} | {:error, term()}
|
||||
def fingerprint(%Context{ref: ref}), do: NIF.get_fingerprint(ref)
|
||||
|
||||
@doc "Get the raw fingerprint as a list of unsigned 32-bit integers."
|
||||
@spec raw_fingerprint(Context.t()) :: {:ok, raw_fingerprint()} | {:error, term()}
|
||||
def raw_fingerprint(%Context{ref: ref}), do: NIF.get_raw_fingerprint(ref)
|
||||
|
||||
@doc "Get a compact 32-bit hash of the fingerprint."
|
||||
@spec hash(Context.t()) :: {:ok, non_neg_integer()} | {:error, term()}
|
||||
def hash(%Context{ref: ref}), do: NIF.get_fingerprint_hash(ref)
|
||||
|
||||
@doc """
|
||||
Encode a raw fingerprint to its compressed form.
|
||||
|
||||
## Options
|
||||
* `:algorithm` - required.
|
||||
* `:base64` - defaults to `true`. When `false`, returns the raw byte form.
|
||||
"""
|
||||
@spec encode(raw_fingerprint(), keyword()) :: {:ok, binary()} | {:error, term()}
|
||||
def encode(raw, opts) when is_list(raw) do
|
||||
algorithm = Keyword.fetch!(opts, :algorithm)
|
||||
base64? = Keyword.get(opts, :base64, true)
|
||||
|
||||
with {:ok, code} <- algorithm_to_code(algorithm) do
|
||||
NIF.encode_fingerprint(raw, code, base64?)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decode a compressed fingerprint.
|
||||
|
||||
## Options
|
||||
* `:base64` - defaults to `true`.
|
||||
|
||||
Returns `{:ok, %{algorithm: atom(), raw: [non_neg_integer()]}}`.
|
||||
"""
|
||||
@spec decode(binary(), keyword()) ::
|
||||
{:ok, %{algorithm: algorithm() | non_neg_integer(), raw: raw_fingerprint()}}
|
||||
| {:error, term()}
|
||||
def decode(encoded, opts \\ []) when is_binary(encoded) do
|
||||
base64? = Keyword.get(opts, :base64, true)
|
||||
|
||||
case NIF.decode_fingerprint(encoded, base64?) do
|
||||
{:ok, {algo_code, raw}} ->
|
||||
{:ok, %{algorithm: code_to_algorithm(algo_code), raw: raw}}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
One-shot fingerprint of an entire PCM buffer.
|
||||
|
||||
## Options
|
||||
* `:sample_rate` - required.
|
||||
* `:channels` - required.
|
||||
* `:algorithm` - defaults to `:test2`.
|
||||
"""
|
||||
@spec compute(iodata(), keyword()) :: {:ok, String.t()} | {:error, term()}
|
||||
def compute(pcm, opts) do
|
||||
with {:ok, ctx} <- new(opts),
|
||||
:ok <- start(ctx, opts),
|
||||
:ok <- feed(ctx, pcm),
|
||||
:ok <- finish(ctx) do
|
||||
fingerprint(ctx)
|
||||
end
|
||||
end
|
||||
|
||||
defp algorithm_to_code(algo) when is_map_key(@algorithms, algo),
|
||||
do: {:ok, Map.fetch!(@algorithms, algo)}
|
||||
|
||||
defp algorithm_to_code(code) when is_integer(code), do: {:ok, code}
|
||||
defp algorithm_to_code(other), do: {:error, {:unknown_algorithm, other}}
|
||||
|
||||
defp code_to_algorithm(code), do: Map.get(@algorithm_codes, code, code)
|
||||
end
|
||||
Reference in New Issue
Block a user