Current section

Files

Jump to
membrane_matroska_plugin lib membrane_matroska parser helper.ex
Raw

lib/membrane_matroska/parser/helper.ex

defmodule Membrane.Matroska.Parser.Helper do
@moduledoc false
# Module for parsing a Matroska / MKV binary stream (such as from a file) used by `Membrane.Matroska.Demuxer`.
# A Matroska file is defined as a Matroska file that contains one segment and satisfies strict constraints.
# A Matroska file is an EBML file (Extendable-Binary-Meta-Language) satisfying certain other constraints.
# Docs:
# - EBML https://datatracker.ietf.org/doc/html/rfc8794
# - Matroska https://www.webmproject.org/docs/container/
# - Matroska https://matroska.org/technical/basics.html
# The module extracts top level elements of the [Matroska Segment](https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-08.html#section-7)
# and incrementally passes these parsed elements forward.
# All top level elements other than `Cluster` occur only once and contain metadata whereas a `Cluster` element holds all the tracks'
# encoded frames grouped by timestamp. It is RECOMMENDED that the size of each individual Cluster Element be limited to store no more than
# 5 seconds or 5 megabytes.
alias Membrane.Matroska.Parser.EBML
# Main function used for parsing a file
@spec parse(binary, function) :: {parsed :: list, unparsed :: binary}
def parse(unparsed, schema \\ &Membrane.Matroska.Schema.deserialize_matroska/1) do
do_parse([], unparsed, schema)
end
@spec parse_many!(list, binary, function) :: list
def parse_many!(acc, bytes, schema) do
case maybe_parse_element(bytes, schema) do
{:ok, {element, <<>>}} ->
[element | acc]
{:ok, {element, rest}} ->
parse_many!([element | acc], rest, schema)
end
end
@spec do_parse(list, binary, function) :: {list, binary}
defp do_parse(acc, bytes, schema) do
case maybe_parse_element(bytes, schema) do
{:error, :need_more_bytes} ->
{acc, bytes}
{:ok, {element, <<>>}} ->
{[element | acc], <<>>}
{:ok, {element, rest}} ->
do_parse([element | acc], rest, schema)
end
end
@spec maybe_parse_element(binary, function) ::
{:error, :need_more_bytes} | {:ok, {{atom, list}, binary}}
defp maybe_parse_element(bytes, schema) do
with {:ok, {element_name, rest}} <- EBML.decode_element_name(bytes),
{:ok, {_element_width, rest}} <- EBML.decode_vint(rest) do
if schema.(element_name) == :ApplyFlatParsing do
maybe_parse_element(rest, schema)
else
maybe_parse_recursively(bytes, schema)
end
end
end
@spec maybe_parse_recursively(binary, function) ::
{:error, :need_more_bytes} | {:ok, {{atom, list}, binary}}
defp maybe_parse_recursively(bytes, schema) do
with {:ok, {name, data, rest}} <- EBML.decode_element(bytes) do
parsing_function = schema.(name)
if parsing_function == (&EBML.parse_master/2) do
{:ok, {{name, EBML.parse_master(data, schema)}, rest}}
else
{:ok, {{name, parsing_function.(data)}, rest}}
end
end
end
end