Packages
membrane_core
0.1.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.7
1.2.6
1.2.5
retired
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc1
1.1.2
1.1.1
1.1.0
1.1.0-rc1
1.1.0-rc0
1.0.1
1.0.0
1.0.0-rc1
1.0.0-rc0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
retired
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
Membrane Multimedia Framework (Core)
Current section
Files
Jump to
Current section
Files
lib/membrane_core/caps/matcher.ex
defmodule Membrane.Caps.Matcher do
@moduledoc """
Module that allows to specify valid caps and verify that they match specification.
Caps specifications (specs) should be in one of the formats:
* simply module name of the desired caps (e.g. `Membrane.Caps.Audio.Raw` or `Raw` with proper alias)
* tuple with module name and keyword list of specs for specific caps fields (e.g. `{Raw, format: :s24le}`)
* list of the formats described above
Field values can be specified in following ways:
* By a raw value for the field (e.g. `:s24le`)
* Using `range/2` for values comparable with `Kernel.<=/2` and `Kernel.>=/2` (e.g. `Matcher.range(0, 255)`)
* With `one_of/1` and a list of valid values (e.g `Matcher.one_of([:u8, :s16le, :s32le])`)
Checks on the values from list are performed recursivly i.e. it can contain another `range/2`,
for example `Matcher.one_of([0, Matcher.range(2, 4), Matcher.range(10, 20)])`
If the specs are defined inside of `Membrane.Element.Base.Mixin.SinkBehaviour.def_known_sink_pads/1` and
`Membrane.Element.Base.Mixin.SourceBehaviour.def_known_source_pads/1` module name can be ommitted from
`range/2` and `one_of/1` calls.
"""
import Kernel, except: [match?: 2]
require Record
alias Membrane.Helper
@type caps_spec_t :: module() | {module(), keyword()}
@type caps_specs_t :: :any | caps_spec_t() | [caps_spec_t()]
@opaque range_spec_t :: {__MODULE__.Range, any, any}
Record.defrecordp(:range_t, __MODULE__.Range, min: 0, max: :infinity)
@opaque list_spec_t :: {__MODULE__.In, list()}
Record.defrecordp(:in_t, __MODULE__.In, list: [])
@doc """
Returns opaque `t:range_spec_t/0` that specifies range of valid values for caps field
"""
@spec range(any, any) :: range_spec_t
def range(min, max) do
range_t(min: min, max: max)
end
@doc """
Returns opaque `t:list_spec_t/0` that specifies list of valid values for caps field
"""
@spec one_of(list()) :: list_spec_t
def one_of(values) when is_list(values) do
in_t(list: values)
end
@doc """
Function used to make sure caps specs are valid.
In particular, valid caps:
* Have shape described by `t:caps_specs_t/0` type
* If they contain keyword list, the keys are present in requested caps type
It returns `:ok` when caps are valid and `{:error, reason}` otherwise
"""
@spec validate_specs(caps_specs_t() | any()) :: :ok | {:error, reason :: tuple()}
def validate_specs(specs_list) when is_list(specs_list) do
specs_list |> Helper.Enum.each_with(&validate_specs/1)
end
def validate_specs({type, keyword_specs}) do
caps = type.__struct__
caps_keys = caps |> Map.from_struct() |> Map.keys() |> MapSet.new()
spec_keys = keyword_specs |> Keyword.keys() |> MapSet.new()
if MapSet.subset?(spec_keys, caps_keys) do
:ok
else
invalid_keys = spec_keys |> MapSet.difference(caps_keys) |> MapSet.to_list()
{:error, {:invalid_keys, type, invalid_keys}}
end
end
def validate_specs(specs) when is_atom(specs), do: :ok
def validate_specs(specs), do: {:error, {:invalid_specs, specs}}
@doc """
Function determining whether the caps match provided specs.
When `:any` is used as specs, caps can by anything (i.e. they can be invalid)
"""
@spec match?(:any, any()) :: true
@spec match?(caps_specs_t(), struct()) :: boolean()
def match?(:any, _), do: true
def match?(specs, %_{} = caps) when is_list(specs) do
specs |> Enum.any?(fn spec -> match?(spec, caps) end)
end
def match?({type, keyword_specs}, %caps_type{} = caps) do
type == caps_type && keyword_specs |> Enum.all?(fn kv -> kv |> match_caps_entry(caps) end)
end
def match?(type, %caps_type{}) when is_atom(type) do
type == caps_type
end
defp match_caps_entry({spec_key, spec_value}, %{} = caps) do
{:ok, caps_value} = caps |> Map.fetch(spec_key)
match_value(spec_value, caps_value)
end
defp match_value(in_t(list: specs), value) when is_list(specs) do
specs |> Enum.any?(fn spec -> match_value(spec, value) end)
end
defp match_value(range_t(min: min, max: max), value) do
min <= value && value <= max
end
defp match_value(spec, value) do
spec == value
end
end