Last active
April 17, 2024 15:02
-
-
Save rrosiek/4cf50a3057bb2efb4f6ed89aad1ee480 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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