defmodule MagickBytes do @moduledoc """ A tiny library for inferring MIME types from magic bytes. """ alias MagickBytes.Matcher @doc """ Tries to infer the MIME type of the file at `path` from its magic bytes. ## Examples iex> MagickBytes.mime("./test/archives/my_gzip_file_disguised_as.txt") {:ok, "application/gzip"} """ @spec mime(path :: Path.t()) :: {:ok, String.t()} | {:error, atom()} def mime(path) when is_binary(path) do with {:ok, file} <- File.open(path, [{:read_ahead, 640}]), result <- try_match(file), :ok <- File.close(file) do result end end defp try_match(file) do with {:ok, byte} <- :file.pread(file, 0, 1), {:continue, :no_match, _bytes} <- Matcher.match(byte) do try_match(file, 1, byte) else {:error, :einval} -> {:error, :no_match} {:match, match} -> {:ok, match} end end defp try_match(_file, 640, _), do: {:error, :no_match} defp try_match(file, current_offset, bytes_read) do with {:ok, byte} <- :file.pread(file, current_offset, 1), {:continue, :no_match, bytes} <- Matcher.match(<> <> <>) do try_match(file, current_offset + 1, bytes) else {:error, :einval} -> {:error, :no_match} {:match, match} -> {:ok, match} error -> error end end end