Skip to content

Instantly share code, notes, and snippets.

@rrosiek
Created Aug 1, 2021
Embed
What would you like to do?
defmodule Media.Mp4 do
def parse(file_path) do
Stream.resource(fn -> open_file(file_path) end, &read_next/1, &close_file/1)
|> Enum.to_list()
|> Enum.filter(&(&1 != nil))
|> List.first()
|> seek_gpmf
end
defp open_file(file_path) do
{:ok, file} = :file.open(file_path, [:raw, :binary, :read])
file
end
defp close_file(file) do
:file.close(file)
end
defp read_next(file) do
case :file.read(file, 8) do
:eof ->
{:halt, file}
{:ok, <<box_length::size(32), "moov", _::binary>>} ->
{:ok, data} = :file.read(file, box_length - 8)
{[data], file}
{:ok, <<box_length::size(32), _::binary>>} ->
{:ok, _} = :file.read(file, box_length - 8)
{[nil], file}
end
end
@doc """
Takes in the moov box and parses it down to a single stbl box containing
the necessary data to parse gpmf data.
stsz has samples:
sample_sizes: 5844, 5676, etc
sample_count: 32
"""
defp seek_gpmf(moov_data) do
parse_boxes(moov_data)
|> Keyword.get_values(:trak)
|> Enum.flat_map(&parse_boxes(&1))
|> Keyword.get_values(:mdia)
|> Enum.flat_map(&parse_boxes(&1))
|> Keyword.get_values(:minf)
|> Enum.flat_map(&parse_boxes(&1))
|> Keyword.get_values(:stbl)
|> Enum.filter(&has_gpmd?(&1))
|> Enum.flat_map(&parse_boxes(&1))
end
defp has_gpmd?(stbl) do
parse_boxes(stbl)
|> Keyword.fetch(:stsd)
|> case do
{:ok, <<_::size(96), "gpmd", _::binary>>} -> true
_ -> false
end
end
defp parse_boxes(binary, boxes \\ [])
defp parse_boxes(<<box_length::size(32), box_type::bitstring-size(32), data::binary>>, boxes) do
box_length = box_length - 8
<<box_data::binary-size(box_length), remaining::binary>> = data
parse_boxes(remaining, [{String.to_atom(box_type), box_data} | boxes])
end
defp parse_boxes(_, boxes), do: boxes
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment