# `MagicBytes`
[🔗](https://github.com/leftstanding/magic_bytes/blob/main/lib/magic_bytes.ex#L1)

Detect MIME types from binary content using magic byte signatures.

Only the leading bytes needed to identify the format are examined — 16 bytes
satisfies all built-in signatures.

    iex> MagicBytes.from_binary(<<0xFF, 0xD8, 0xFF, 0xE0>>)
    {:ok, "image/jpeg"}

    iex> MagicBytes.from_binary(<<0x00, 0x00, 0x00, 0x00>>)
    {:error, :unknown}

## Guards

For each signature a corresponding guard macro is generated,
named `is_<mime_with_slashes_and_hyphens_as_underscores>`. These expand
to pure boolean expressions and can be used in `when` clauses or regular
code after `require MagicBytes`:

    iex> require MagicBytes
    iex> MagicBytes.is_image_jpeg(<<0xFF, 0xD8, 0xFF, 0xE0>>)
    true

    iex> require MagicBytes
    iex> MagicBytes.is_application_pdf(<<?%, ?P, ?D, ?F, ?-, ?1, ?., ?7>>)
    true

Guards are not generated for container-format signatures that require
inspecting bytes beyond a fixed prefix (WebP, WAV, AVI, AIFF, MP4,
HEIC, AVIF, QuickTime). Use `from_binary/1` for those.

## Custom signatures

Define a module with `use MagicBytes.DefineSignatures`, configure it once,
and all `from_*` functions will check your signatures first, falling back to
the built-ins automatically.

    defmodule MyApp.Signatures do
      use MagicBytes.DefineSignatures, guards: true
      defsignature("application/x-cld", <<0xCA, 0xFE, 0xD0, 0x0D>>)
    end

    # config/config.exs
    config :magic_bytes, extra_signatures: MyApp.Signatures

Passing `guards: true` generates guard macros on your module
(e.g. `MyApp.Signatures.is_application_x_cld/1`)
that can be used in `when` clauses after `require MyApp.Signatures`.

## Configuration

Set in `config/config.exs` (values are resolved at compile time):

| Key                | Type         | Default | Description |
|--------------------|--------------|---------|-------------|
| `:extra_signatures`| module       | `nil`   | Module with additional signatures |
| `:read_bytes`      | pos_integer  | `16`    | Bytes read from input. Only needed if you define custom signatures whose prefix exceeds 16 bytes. |
| `:only`            | list(String) | `nil`   | When set, only these MIME types are returned; others become `{:error, :unknown}` |
| `:exclude`         | list(String) | `[]`    | MIME types to suppress; ignored when `:only` is set |

## Supported formats

| Category    | MIME types |
|-------------|------------|
| Images      | `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/bmp`,
|               `image/tiff`, `image/x-icon`, `image/vnd.adobe.photoshop`,
|               `image/heic`, `image/avif`, `image/jp2`, `image/jxl`, `image/flif` |
| Audio       | `audio/mpeg`, `audio/flac`, `audio/ogg`, `audio/wav`, `audio/aiff`, `audio/mp4` |
| Video       | `video/mp4`, `video/quicktime`, `video/x-matroska`, `video/x-flv`, `video/x-msvideo` |
| Documents   | `application/pdf`, `application/zip`, `application/x-cfb`, `application/rtf` |
| Archives    | `application/x-rar-compressed`, `application/x-7z-compressed`, `application/gzip`,
|             | `application/x-bzip2`, `application/x-xz`, `application/zstd`, `application/x-lz4` |
| Data        | `application/vnd.apache.parquet`, `application/vnd.apache.arrow.file` |
| Executables | `application/x-elf`, `application/x-msdownload`, `application/x-mach-binary`,
|             | `application/wasm`, `application/vnd.android.dex` |
| Fonts       | `font/woff`, `font/woff2`, `font/otf`, `font/ttf` |
| Database    | `application/x-sqlite3` |

# `error`

```elixir
@type error() :: {:error, :unreadable | :unknown}
```

# `mime_type`

```elixir
@type mime_type() :: String.t()
```

# `from_binary`

```elixir
@spec from_binary(binary()) :: {:ok, mime_type()} | error()
```

Detects the MIME type from a binary.

Only the leading bytes are examined; passing the full file content is
fine but unnecessary.

## Examples

    iex> MagicBytes.from_binary(<<0xFF, 0xD8, 0xFF, 0xE0>>)
    {:ok, "image/jpeg"}

    iex> MagicBytes.from_binary(<<0x89, "PNG", 0x0D, 0x0A, 0x1A, 0x0A>>)
    {:ok, "image/png"}

    iex> MagicBytes.from_binary(<<?%, ?P, ?D, ?F>>)
    {:ok, "application/pdf"}

    iex> MagicBytes.from_binary(<<0x1F, 0x8B>>)
    {:ok, "application/gzip"}

    iex> MagicBytes.from_binary(<<0x00, 0x00, 0x00, 0x00>>)
    {:error, :unknown}

# `from_path`

```elixir
@spec from_path(Path.t()) :: {:ok, mime_type()} | error()
```

Detects the MIME type of the file at `path` by reading its leading bytes.

Returns `{:error, :unreadable}` if the file cannot be opened.

## Examples

    iex> MagicBytes.from_path("test/fixtures/fixture.jpg")
    {:ok, "image/jpeg"}

    iex> MagicBytes.from_path("test/fixtures/fixture.png")
    {:ok, "image/png"}

    iex> MagicBytes.from_path("test/fixtures/fixture.pdf")
    {:ok, "application/pdf"}

    iex> MagicBytes.from_path("/nonexistent/file.jpg")
    {:error, :unreadable}

# `from_stream`

```elixir
@spec from_stream(Enumerable.t()) :: {:ok, mime_type()} | error()
```

Detects the MIME type from a stream of binaries.

Chunks are accumulated until enough bytes are available, then detection
runs on the combined header. The stream is not fully consumed.

Returns `{:error, :unreadable}` if the stream is empty.

## Examples

    iex> MagicBytes.from_stream([<<0xFF, 0xD8, 0xFF, 0xE0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>])
    {:ok, "image/jpeg"}

    iex> MagicBytes.from_stream([<<0xFF, 0xD8>>, <<0xFF, 0xE0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>])
    {:ok, "image/jpeg"}

    iex> MagicBytes.from_stream([])
    {:error, :unreadable}

# `is_application_gzip`
*macro* 

# `is_application_pdf`
*macro* 

# `is_application_rtf`
*macro* 

# `is_application_vnd_android_dex`
*macro* 

# `is_application_vnd_apache_arrow_file`
*macro* 

# `is_application_vnd_apache_parquet`
*macro* 

# `is_application_wasm`
*macro* 

# `is_application_x_7z_compressed`
*macro* 

# `is_application_x_bzip2`
*macro* 

# `is_application_x_cfb`
*macro* 

# `is_application_x_elf`
*macro* 

# `is_application_x_lz4`
*macro* 

# `is_application_x_mach_binary`
*macro* 

# `is_application_x_msdownload`
*macro* 

# `is_application_x_rar_compressed`
*macro* 

# `is_application_x_sqlite3`
*macro* 

# `is_application_x_xz`
*macro* 

# `is_application_zip`
*macro* 

# `is_application_zstd`
*macro* 

# `is_audio_flac`
*macro* 

# `is_audio_mpeg`
*macro* 

# `is_audio_ogg`
*macro* 

# `is_font_otf`
*macro* 

# `is_font_ttf`
*macro* 

# `is_font_woff2`
*macro* 

# `is_font_woff`
*macro* 

# `is_image_bmp`
*macro* 

# `is_image_flif`
*macro* 

# `is_image_gif`
*macro* 

# `is_image_jp2`
*macro* 

# `is_image_jpeg`
*macro* 

# `is_image_jxl`
*macro* 

# `is_image_png`
*macro* 

# `is_image_tiff`
*macro* 

# `is_image_vnd_adobe_photoshop`
*macro* 

# `is_image_x_icon`
*macro* 

# `is_video_x_flv`
*macro* 

# `is_video_x_matroska`
*macro* 

---

*Consult [api-reference.md](api-reference.md) for complete listing*
