Skip to main content

lib/iso_media/boxes/track_header.ex

defmodule ISOMedia.Boxes.TrackHeader do
  @moduledoc """
  Typed view of the `tkhd` Track Header Box. Exposes `track_id`/`duration`
  (and creation/modification times); trailing fields are preserved in `rest`.
  """

  alias ISOMedia.{Box, FullBox}

  defstruct [:version, :flags, :creation_time, :modification_time, :track_id, :duration, :rest]

  @type t :: %__MODULE__{
          version: non_neg_integer(),
          flags: <<_::24>>,
          creation_time: non_neg_integer(),
          modification_time: non_neg_integer(),
          track_id: non_neg_integer(),
          duration: non_neg_integer(),
          rest: binary()
        }

  @doc "Decode a `tkhd` box into a `%TrackHeader{}`."
  def decode(%Box{type: "tkhd", data: data}) do
    {version, flags, body} = FullBox.parse(data)
    {ctime, mtime, track_id, duration, rest} = split(version, body)

    %__MODULE__{
      version: version,
      flags: flags,
      creation_time: ctime,
      modification_time: mtime,
      track_id: track_id,
      duration: duration,
      rest: rest
    }
  end

  @doc "Encode a `%TrackHeader{}` back into a `tkhd` box."
  def encode(%__MODULE__{version: 0} = h) do
    body = [
      <<h.creation_time::32, h.modification_time::32, h.track_id::32, 0::32, h.duration::32>>,
      h.rest
    ]

    wrap(h, body)
  end

  def encode(%__MODULE__{version: 1} = h) do
    body = [
      <<h.creation_time::64, h.modification_time::64, h.track_id::32, 0::32, h.duration::64>>,
      h.rest
    ]

    wrap(h, body)
  end

  defp split(0, <<c::32, m::32, id::32, _res::32, d::32, rest::binary>>), do: {c, m, id, d, rest}
  defp split(1, <<c::64, m::64, id::32, _res::32, d::64, rest::binary>>), do: {c, m, id, d, rest}

  defp wrap(h, body) do
    %Box{type: "tkhd", data: IO.iodata_to_binary(FullBox.encode(h.version, h.flags, body))}
  end
end