# `Storehouse.Image.JPEG`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.2.0/lib/storehouse/image/jpeg.ex#L1)

Edits the metadata inside a JPEG without re-encoding it: removes where a
photo was taken, keeps only a few tags, or writes an author's name, and
never touches the image's data.

A JPEG's metadata lives in segments of its own, before the image data:
Exif (APP1), XMP (APP1), IPTC (APP13), comments. These functions rewrite
those segments and copy everything else, the tables and the compressed
image, byte for byte, so the pixels a reader decodes are the same ones.
Re-encoding with an image library (libvips, ImageMagick) would lose
quality and change the file.

    {:ok, jpeg} = Storehouse.Image.JPEG.strip(jpeg, :gps)
    {:ok, jpeg} = Storehouse.Image.JPEG.strip(jpeg, {:keep, [:orientation, :copyright]})
    {:ok, jpeg} = Storehouse.Image.JPEG.put_tags(jpeg, %{artist: "Ana Sousa", copyright: "© Ana Sousa"})

`edit_file/3` does the same from a file to a file, with only the
metadata in memory: what `Storehouse.Image.MetadataEditor.Builtin`, the
default editor of the `Storehouse.Analyzer.EditMetadata` step, uses.

Nothing in Storehouse calls these on its own: a stored file's bytes
change only when the app asks, with these functions or that step.

## Tags

By name, from the first directory (IFD0): `:orientation`, `:artist`, `:copyright`, `:image_description`, `:make`, `:model`, `:software`, `:date_time`, `:x_resolution`, `:y_resolution`, `:resolution_unit`;
and from the Exif directory: `:date_time_original`, `:offset_time_original`, `:exposure_time`, `:f_number`, `:iso`, `:focal_length`, `:lens_make`, `:lens_model`, `:color_space`.

## What is left as it is

  * Data after the end of the image: a phone's gain map or depth map,
    a second picture its MPF segment lists. Its offsets are kept right
    when segments before it change size.
  * Place names: `strip(jpeg, :gps)` removes coordinates, not a city an
    app or a person wrote in IPTC or XMP.
  * Other formats: these functions refuse them with `{:error,
    :not_jpeg}`. `Storehouse.Image.PNG` and `Storehouse.Image.WebP` edit those two
    the same way; HEIC and TIFF are left to another editor
    (`Storehouse.Image.MetadataEditor`).

A JPEG whose segments or Exif cannot be read is refused with `{:error,
:malformed}` or `{:error, :malformed_exif}`, never cut short.

# `reason`

```elixir
@type reason() :: :not_jpeg | :malformed | :malformed_exif | :exif_too_large
```

Why a JPEG is refused.

# `tag`

```elixir
@type tag() :: atom()
```

A tag, by name (see Tags).

# `edit`

```elixir
@spec edit(binary(), Storehouse.Image.MetadataEditor.changes()) ::
  {:ok, binary()} | {:error, reason()}
```

The JPEG with `changes` made: `:strip` (as `strip/2`), then `:put` (as
`put_tags/2`).

# `edit_file`

```elixir
@spec edit_file(Path.t(), Path.t(), Storehouse.Image.MetadataEditor.changes()) ::
  :ok | :unchanged | {:error, reason() | File.posix()}
```

The JPEG at `from`, with `changes` made (as `edit/2`), written to `to`.

Only its metadata is read into memory: the image data, most of the
file, is copied from file to file, so a photo of any size takes the
memory of its metadata. `:unchanged` when there is nothing to change,
and `to` is not written.

# `put_tags`

```elixir
@spec put_tags(binary(), %{optional(tag()) =&gt; String.t() | nil}) ::
  {:ok, binary()} | {:error, reason()}
```

The JPEG with these tags written, or removed when `nil`: `:artist`,
`:copyright` and `:image_description`, as UTF-8 text, the way ExifTool
writes it (a reader that takes Exif text as Latin-1 shows letters past
ASCII wrongly). A JPEG with no Exif is given one.

The tags it replaces and their old directory are zeroed, so an old name
does not stay in the file.

## Examples

    iex> {:ok, jpeg} = Storehouse.Image.JPEG.put_tags(File.read!("test/fixtures/harbour.jpg"), %{artist: "Ana"})
    iex> Storehouse.Image.JPEG.tags(jpeg)
    {:ok, %{artist: "Ana"}}

# `strip`

```elixir
@spec strip(binary(), :gps | {:keep, [tag()]}) :: {:ok, binary()} | {:error, reason()}
```

The JPEG without the metadata `what` names:

  * `:gps` - where it was taken: the Exif GPS directory, and the GPS
    properties of its XMP. The bytes they held are zeroed, not only
    unlinked. Everything else stays where it is.
  * `{:keep, tags}` - every tag but those named (see Tags), and all of
    XMP, IPTC, comments and the Exif thumbnail. What draws the image
    stays: the JFIF header, the ICC colour profile, Adobe's colour
    transform and the MPF index. XMP holds a phone's HDR gain map
    description too, which goes with it.

A JPEG with nothing to remove is given back as it was, byte for byte.

# `tags`

```elixir
@spec tags(binary()) :: {:ok, %{required(tag()) =&gt; term()}} | {:error, reason()}
```

The named tags (see Tags) the JPEG's Exif has, decoded: text as a
string, numbers as integers, fractions as `{numerator, denominator}`.
`%{}` for a JPEG with no Exif.

---

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