ImagePipe (image_pipe v0.1.0)

Copy Markdown View Source

Processes images in your own code, and holds the configuration that ImagePipe.Plug serves images with.

builder =
  ImagePipe.URL.new()
  |> ImagePipe.URL.group(resize: [width: 400, height: 300, fit: :cover])
  |> ImagePipe.URL.output(format: :webp)

{:ok, result} = ImagePipe.run(ImagePipe.config(), builder, {:file, "cat.jpg"})

config/1 builds a configuration, ImagePipe.URL describes what to do to the image, and run/4 returns the result. write/5 writes it to a file instead. Processing images in Elixir walks through it.

An instance is a supervised process that holds a configuration and starts the processes its caches need. Run one with child_spec/1 when a mount's configuration reads runtime values, such as environment variables, or when the configuration uses a bounded cache. ImagePipe.Plug mounts, run/4, write/5, validate/2, and url_config/2 find it by name.

Summary

Functions

Returns a child specification that runs a named ImagePipe instance.

Builds the configuration that ImagePipe.Plug mounts, instances, and run/4 use.

Runs a builder's plan on an image and returns the encoded result.

Returns the URL settings a configuration or a running instance serves, for building URLs with ImagePipe.URL.new/1.

Checks a builder's plan as this configuration would serve it.

Runs a builder's plan like run/4, then writes the result to path.

Functions

child_spec(options)

@spec child_spec(keyword()) :: Supervisor.child_spec()

Returns a child specification that runs a named ImagePipe instance.

Start the instance in your application's supervision tree, before the endpoint that mounts it. Its options are evaluated when the application starts, so they can read environment variables:

children = [
  {ImagePipe,
   name: MyApp.Images,
   sources: [...],
   cache:
     {ImagePipe.Cache.FileSystem,
      root: "/var/cache/image_pipe/processed",
      max_size_bytes: 5_000_000_000,
      node_id: "node-0"}},
  MyAppWeb.Endpoint
]

Mount it with the :instance option of ImagePipe.Plug, and pass its name to run/4, write/5, validate/2, and url_config/2.

A bounded ImagePipe.Cache.FileSystem runs processes that track the cache's size, and only an instance starts them. A configuration with such a cache must be used through an instance. An inline ImagePipe.Plug mount, and run/4 with a configuration from config/1, raise ArgumentError for it. Setting one up is covered in Caching processed images.

Options

  • :name (atom/0) - Required. Name that ImagePipe.Plug mounts, ImagePipe.run/4, ImagePipe.write/5, ImagePipe.validate/2, and ImagePipe.url_config/2 use to find the instance. It also names the instance's supervisor.

  • :mounts (keyword/0) - Named sets of URL options, such as [signed: [keys: [key]]]. An ImagePipe.Plug mount picks one with its :mount option, so mounts with different signing keys share one instance and its caches. Each set takes the URL options of ImagePipe.config/1 (:base_url, :keys, :encrypt_source, :source_encryption_keys, :iv_mode) and applies them on top of the instance's own, so an option a set leaves out keeps the instance's value. For a mount that doesn't check signatures, set keys: [], and source_encryption_keys: [] if the instance has them. The default value is [].

  • :config (ImagePipe.Config.t/0) - A configuration from ImagePipe.config/1 to start from. The other options override it.

  • :detector_warmup (:all, false, or a list of class names) - Which detector classes to load models for when the instance starts, using ImagePipe.Transform.Detector.Warmup. :all loads every model, a list such as ["face"] loads only the models those classes need, and false loads none. Nothing is loaded when the configuration's detector isn't available. A class the detector doesn't support raises ArgumentError. The default value is :all.

Every option of config/1 is accepted too. Invalid options raise ArgumentError when the child specification is built, before the instance starts.

config(options \\ [])

@spec config(keyword()) :: ImagePipe.Config.t()

Builds the configuration that ImagePipe.Plug mounts, instances, and run/4 use.

config =
  ImagePipe.config(
    sources: [
      media: [
        adapter: ImagePipe.Source.File,
        match: :path,
        options: [root: "/srv/images", root_id: "media"]
      ]
    ],
    presets: %{"card" => "w=400/h=300/fit=cover"},
    quality: 82
  )

Invalid options raise ArgumentError, without credentials in the message. Building a configuration reads no sources or caches. Inspecting one hides its values.

Elixir configuration shows which settings belong here and which belong to a mount, a source, or a request.

keys and source_encryption_keys set which URLs an ImagePipe.Plug mount accepts. base_url, encrypt_source, and iv_mode only affect URLs built from the configuration with url_config/2.

Options

  • :base_url (String.t/0) - Prefix for generated URLs: an HTTP(S) URL such as "https://cdn.example.com/images", a root-relative path such as "/images", or a relative path such as "images". A trailing / is removed. Path segments use unescaped ASCII letters, digits, -, ., _, or ~. A base URL with credentials, a query string, a fragment, an empty segment, or a . or .. segment raises. With the default, URLs start with /, such as /w=400/src/cat.jpg. The default value is "".

  • :keys (list of hex-encoded String.t/0) - Signing keys, each at least 64 hex digits (32 bytes), such as the output of openssl rand -hex 32. A shorter key raises ArgumentError. The first key signs generated URLs. The server accepts a signature from any key in the list, so keep an old key while URLs signed with it are in use. Without keys, URLs are unsigned and the server rejects signed ones. The default value is [].

  • :encrypt_source (boolean/0) - Encrypts the source, and any watermark source, in generated URLs, which then use enc/<token> instead of src/<path>. Requires :source_encryption_keys. A server with encryption keys decrypts enc/ sources regardless of this option. The default value is false.

  • :source_encryption_keys (list of hex-encoded String.t/0) - Keys that encrypt and decrypt sources, each 32 bytes written as 64 hex digits in either case, such as the output of openssl rand -hex 32. The first key encrypts. The server decrypts with any key in the list. The keys must differ from the signing keys, and setting them requires :keys. The default value is [].

  • :iv_mode (:deterministic or :random) - How ImagePipe.URL.url/3 picks the initialization vector for an encrypted source. :deterministic derives it from the source with a secret key, so one source always gives the same URL. :random gives a new URL on every call. The default value is :deterministic.

  • :sources (keyword/0) - Named sources that originals are read from, as name: [adapter: module, match: rule, options: [...]]. See sources.

  • :source_cache_policy (keyword/0) - Default cache storage and freshness policy for every source. See ImagePipe.Source.CachePolicy and source cache settings.

  • :max_body_bytes (pos_integer/0) - Maximum size of an original, in bytes. A larger original fails the request. The default value is 10000000.

  • :max_input_pixels (pos_integer/0) - Maximum pixels of a decoded original. For an animation, counts the frames composited to reach the requested page. A larger original fails the request. The default value is 40000000.

  • :max_input_frames (pos_integer/0) - Maximum frames or pages an original may declare. An original with more fails the request. The default value is 1000.

  • :max_result_width (pos_integer/0) - Maximum output width. Larger results are scaled down to fit. The default value is 8192.

  • :max_intermediate_pixels (pos_integer/0) - Maximum pixels in a decoded frame buffered, downsampled, or delivered during processing. Larger frames fail with 422. Lazy intermediate frames may be larger when a crop reduces them before those steps. The default value is 100000000.

  • :max_result_height (pos_integer/0) - Maximum output height. Larger results are scaled down to fit. The default value is 8192.

  • :max_result_pixels (pos_integer/0) - Maximum output pixels. Larger results are scaled down to fit. The default value is 40000000.

  • :processing_pool (atom/0 or pid/0) - A running ImagePipe.ProcessingPool, by name or PID, that limits how many images are processed at once. See limiting concurrent processing.

  • :auto_avif (boolean/0) - Serve AVIF when the request's Accept header lists it. The default value is true.

  • :auto_webp (boolean/0) - Serve WebP when the request's Accept header lists it. The default value is true.

  • :format_order (list of :avif and :webp) - Which format wins when Accept lists both. A format left out of the list comes after the listed ones. The default value is [:avif, :webp].

  • :quality (pos_integer/0) - Encoder quality, 1..100, for formats without a :format_quality. The default value is 80.

  • :format_quality (map of atom/0 keys and pos_integer/0 values) - Quality per output format, 1..100. Merged with the defaults, %{webp: 79, avif: 63}. A request's q replaces this table, and its format-q replaces the entries for the formats it lists. See format-q.

  • :strip_metadata (boolean/0) - Remove EXIF, XMP, and other optional metadata from the output. The default value is true.

  • :keep_copyright (boolean/0) - Keep copyright and artist fields when stripping metadata. The default value is true.

  • :stripped_dpi (pos_integer/0) - Density written to the output, 1..65535, when metadata is stripped and the request has no dpi. The default value is 72.

  • :strip_color_profile (boolean/0) - Convert the output to sRGB (or gray) and leave out the original's ICC profile. false keeps the original's profile. The default value is true.

  • :preserve_hdr (boolean/0) - Keep high bit depth in output formats that support it. The default value is false.

  • :skip_processing_formats (list of atom/0) - Original formats, such as [:gif], served unchanged instead of processed when the request names no other format and draws no watermark. The unchanged original keeps its metadata, including any location data, and only :max_body_bytes limits it. See formats. The default value is [].

  • :autoquality (boolean/0) - Picks each image's quality to meet :autoquality_target by encoding and scoring several qualities. A request can turn it on or off and set its own target with autoquality. The default value is false.

  • :autoquality_target - The SSIMULACRA2 score auto-quality aims for, above 0 and up to 100. The default value is 75.

  • :jpeg_options (keyword/0) - Default JPEG encoder settings, as a keyword list with the fields of the jpeg_options: option of ImagePipe.URL.output/2, such as [interlace: true]. A request's settings win field by field. See encoder options and the builder names in URL option names.

  • :png_options (keyword/0) - Default PNG encoder settings, as for :jpeg_options.

  • :webp_options (keyword/0) - Default WebP encoder settings, as for :jpeg_options. :effort defaults to 4.

  • :avif_options (keyword/0) - Default AVIF encoder settings, as for :jpeg_options. :effort defaults to 3 and :subsample_mode to :off.

  • :detector (:default, nil, or module/0) - The detector for face and object detection. :default uses the built-in detector when its dependencies are installed, nil turns detection off, and a module uses a custom detector. See enabling detection. The default value is :default.

  • :detector_required (boolean/0) - Fail requests that ask for detection when it can't run: 501 when the detector can't detect the requested classes in this build, 503 when its models aren't downloaded, and 500 when detection fails. With false, the crop falls back to attention cropping. anchor=smart-face always falls back. With true, ImagePipe.config/1 raises ArgumentError when the detector can't detect any class. The default value is false.

  • :telemetry_prefix (list of atom/0) - Prefix of every telemetry event name. See telemetry. The default value is [:image_pipe].

  • :clock ((-> integer())) - Returns the current Unix time in seconds, for checking a URL's expires. The system clock by default.

  • :cache ({module, keyword}) - Cache for processed images, such as {ImagePipe.Cache.FileSystem, root: "/var/cache/image_pipe/processed"}. See caching processed images. Off by default.

  • :input_cache ({module, keyword}) - Cache for originals fetched from sources, configured like :cache. See originals cache. Off by default.

  • :storage_inputs (list of {:header, name} or {:cookie, name}) - Request headers and cookies whose values select separate cached copies, such as [{:header, "x-tenant"}]. They aren't sent to the source and don't change the image. The default value is [].

  • :watermarks (map of atom/0 to keyword/0) - Named watermark images that requests select with wm=name, as %{logo: [source: "brand/logo.png", opacity: 0.6]}. Names match [a-z0-9_-]+. :source is an image path that a configured source serves. :opacity, above 0 and at most 1, defaults to 1 and multiplies the request's wm-opacity. See watermarks. The default value is %{}.

  • :request_watermarks (boolean/0) - Let requests overlay any image the configured sources serve, with wm-src64 or wm-enc, besides the named :watermarks. The default value is false.

  • :presets (map of String.t/0 to fragment or ImagePipe.URL builder) - Named sets of options that URLs select with preset=name, as URL fragments such as %{"card" => "w=400/h=300/fit=cover"} or ImagePipe.URL builders. See defining presets. The default value is %{}.

  • :request_defaults (fragment or ImagePipe.URL builder) - Options applied to the first group of every request, before presets and the request's own options. One group, with no preset.

  • :preset_lookup ({module, keyword}) - Looks up preset names that :presets doesn't define, per request. See ImagePipe.PresetLookup and storing presets in a database.

  • :max_preset_lookups (pos_integer/0) - Most distinct names one request may look up. Only with :preset_lookup. The default value is 32.

run(config, builder, input, options \\ [])

@spec run(
  ImagePipe.Config.t() | atom(),
  ImagePipe.URL.t(),
  {:file | :binary | :source, binary()},
  keyword()
) :: {:ok, ImagePipe.Result.t()} | {:error, term()}

Runs a builder's plan on an image and returns the encoded result.

builder =
  ImagePipe.URL.new()
  |> ImagePipe.URL.group(resize: [width: 400])
  |> ImagePipe.URL.output(format: :webp)

{:ok, result} = ImagePipe.run(ImagePipe.config(), builder, {:file, "photos/cat.jpg"})
result.content_type
#=> "image/webp"

config comes from config/1, or is the name of a running instance. A configuration with a bounded cache must be passed by its instance's name. Presets and request defaults come from config, as for a request to an ImagePipe.Plug mount. The builder's own URL configuration isn't used. Matching source bytes, plans, settings, and Accept preferences give the same result as an HTTP request.

Inputs

  • {:source, source} - a source the configuration's configured sources resolve, as for an HTTP request: a path, an HTTP(S) URL such as "https://assets.example.com/cat.jpg", an S3 identifier, or a custom scheme. Pass the source without the src/ marker or the URL escaping of the request path. The source's network, redirect, timeout, and content-type policies apply. These inputs use the configuration's caches, so run/4 and HTTP requests reuse each other's stored copies when the plan, Accept preferences, and request inputs match.
  • {:file, path} - a local file, by absolute path or relative to the current working directory. Symlinks are followed, and the path must end at a regular file. The path isn't confined to a directory. Use a {:source, path} with an ImagePipe.Source.File source for that.
  • {:binary, bytes} - an encoded image in memory, such as an upload.

{:file, path} and {:binary, bytes} inputs are never cached. Every input is subject to :max_body_bytes, :max_input_pixels, and :max_input_frames.

Options

Every option of config/1 overrides the configuration for this call, such as max_input_pixels: 50_000_000. Invalid options raise ArgumentError. An instance name takes the same options, but a bounded :cache or :input_cache raises ArgumentError unless it is one of the instance's own caches. run/4 and write/5 also take:

  • :accept (String.t/0) - An HTTP Accept value, such as "image/avif,image/webp", for choosing the output format when the plan has no format. An empty value keeps the original's format where possible, as for a request without Accept (see output formats). :auto_avif, :auto_webp, and :format_order apply as configured. The default value is "".

  • :request_inputs (keyword/0) - The header and cookie values named by the configuration's :storage_inputs, for {:source, source} inputs. They select the same stored copy as those values in an HTTP request. Header names are case-insensitive, and cookie names are case-sensitive. A missing value matches a request without it. They aren't sent to the source and don't change the result. The default value is [].

    • :headers (list of {String.t(), String.t()}) - Header values, such as [{"x-tenant", "one"}]. Names must be valid header names, and values can't contain control characters. The default value is [].

    • :cookies (map of String.t/0 to String.t/0) - Cookie values by name, such as %{"session" => "abc"}. The default value is %{}.

The builder's request controls behave as in a URL. cachebuster selects a separate stored copy. expires is compared with the configuration's :clock, and a plan is still valid at its exact expires second. filename, attachment, and debug don't change the result.

Options that have no effect, such as fit: :cover without a width or height, are ignored. The [:request, :ignored_options] telemetry event reports the ones the builder sets.

Return values

Returns {:ok, %ImagePipe.Result{}}, or {:error, reason}:

  • {:invalid_request, issues} - an option is unknown or has an invalid value, the builder's options don't combine, or it names an unknown preset or watermark. issues is a list of ImagePipe.Plan.Spec.Issue.
  • :expired - the plan's expires time has passed.
  • {:invalid_output, reason} - the output options can't be combined, such as color_profile: {:convert, :srgb} with hdr: :preserve. A configured default counts too.
  • {:unsupported_output_format, format} - no encoder for the format.
  • {:detector, reason} - detection is required and reason is :unavailable or :not_ready, or the plan names detection classes the detector lacks ({:unknown_classes, names}).
  • {:preset, reason} - the configuration's preset lookup failed: :too_many_presets when the plan names more presets to look up than :max_preset_lookups allows, :lookup_unavailable, or :invalid_definition.
  • {:invalid_source, reason} - the input isn't one of the tuples above, the source isn't valid UTF-8, or the source or a watermark_source in the plan can't be parsed.
  • {:source, reason} - the source or a watermark source couldn't be read, such as {:source, :enoent} for a missing file, {:source, :not_found} for a path no configured source matches, or {:source, :body_too_large}.
  • {:input_limit, reason} - the decoded image exceeds :max_input_pixels or :max_input_frames.
  • {:page_out_of_range, page, pages} - the builder's page doesn't exist in the original.
  • {:decode, reason} - the input isn't an image ImagePipe can decode.
  • {:transform, reason} - processing failed, such as a region outside the image.
  • {:encode, reason} or {:encode, exception, stacktrace} - encoding failed.
  • {:processing, reason} - the processing pool rejected or stopped the work, such as :overloaded, :queue_timeout, or :timeout.

The checks that don't need the image run first, so {:invalid_request, _}, :expired, {:invalid_output, _}, {:unsupported_output_format, _}, {:detector, _}, and {:preset, _} return before any source is read. Errors raised by transform code propagate.

Resources

The result is complete when run/4 returns, and holds no open file, source, or image. Source resources are closed before run/4 returns, on success and on failure. The result is held in memory, so running large outputs concurrently needs memory for each complete result.

url_config(config_or_name, options \\ [])

@spec url_config(ImagePipe.Config.t() | atom(), keyword()) :: ImagePipe.URL.Config.t()

Returns the URL settings a configuration or a running instance serves, for building URLs with ImagePipe.URL.new/1.

MyApp.Images
|> ImagePipe.url_config()
|> ImagePipe.URL.new()
|> ImagePipe.URL.group(resize: [width: 400])
|> ImagePipe.URL.url!("images/cat.jpg")

The result carries the configuration's presets, request defaults, and watermark settings as its :validate_against option, so ImagePipe.URL.validate/1 and ImagePipe.URL.url/3 check plans against them.

Options

  • :mount (atom/0) - One of the instance's :mounts, whose URL options the result uses instead of the instance's own.

Raises ArgumentError for an invalid option, when no instance with the name is running or it has no mount with that name, or when :mount comes with a configuration.

validate(config, builder)

@spec validate(ImagePipe.Config.t() | atom(), ImagePipe.URL.t()) ::
  {:ok, [ImagePipe.Plan.Spec.Issue.t()]} | {:error, term()}

Checks a builder's plan as this configuration would serve it.

Applies request defaults and presets, including the preset lookup, then checks the request, its output policy, and its watermark sources. Returns {:ok, warnings}, the ImagePipe.Plan.Spec.Issue warnings for options run/4 would ignore and for mistakes the builder repaired, or the error run/4 would return, without reading a source or accessing a cache.

write(config, builder, input, path, options \\ [])

@spec write(
  ImagePipe.Config.t() | atom(),
  ImagePipe.URL.t(),
  {:file | :binary | :source, binary()},
  Path.t(),
  keyword()
) :: {:ok, ImagePipe.Result.t()} | {:error, term()}

Runs a builder's plan like run/4, then writes the result to path.

{:ok, result} =
  ImagePipe.write(ImagePipe.config(), builder, {:binary, upload}, "thumbs/cat.webp")

Takes the same inputs and options, and returns the same result, as run/4. The file holds result.data, with an :info result written as JSON. An existing file is overwritten.

The output format comes from the plan or from :accept, never from the file name, so "cat.jpg" can hold WebP. Set format: with ImagePipe.URL.output/2 to match the file name.

Returns run/4's errors, or {:error, {:destination, reason}} when the file can't be written, such as {:destination, :enoent} for a missing directory. The file is written after source resources are closed.