Skip to content

Instantly share code, notes, and snippets.

@rrosiek
Last active April 17, 2024 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rrosiek/4cf50a3057bb2efb4f6ed89aad1ee480 to your computer and use it in GitHub Desktop.
Save rrosiek/4cf50a3057bb2efb4f6ed89aad1ee480 to your computer and use it in GitHub Desktop.
defmodule Mp4box do
@moduledoc """
Documentation for `Mp4box`.
"""
def parse(file_path) do
Stream.resource(fn -> open_file(file_path) end, &read_next/1, &close_file/1)
|> Stream.filter(&(&1 != nil))
|> Enum.to_list()
|> 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">>} ->
{:ok, data} = :file.read(file, box_length - 8)
{[data], file}
{:ok, <<box_length::size(32), _::binary>>} when box_length > 1 ->
{:ok, _} = :file.read(file, box_length - 8)
{[nil], file}
{:ok, <<_one::size(32), _mdat::bitstring-size(32)>>} ->
# Skip another 8 bytes because this is a large 16 bytes header and
# the size is on the last 8 bytes.
{:ok, <<box_length::size(64)>>} = :file.read(file, 8)
{:ok, _} = :file.read(file, box_length - 16)
{[nil], file}
end
end
# 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