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.PNGandStorehouse.Image.WebPedit 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.
Summary
Functions
The JPEG with changes made: :strip (as strip/2), then :put (as
put_tags/2).
The JPEG at from, with changes made (as edit/2), written to to.
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 JPEG without the metadata what names
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.
Types
@type reason() :: :not_jpeg | :malformed | :malformed_exif | :exif_too_large
Why a JPEG is refused.
@type tag() :: atom()
A tag, by name (see Tags).
Functions
@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).
@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.
@spec put_tags(binary(), %{optional(tag()) => 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"}}
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.
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.