Packages

ExOptimizer is a tool to optimize pictures by running them through a chain of various image optimization tools.

Current section

Files

Jump to
ex_optimizer lib ex_optimizer.ex
Raw

lib/ex_optimizer.ex

defmodule ExOptimizer do
@moduledoc """
ExOptimizer is a tool to optimize pictures by running them through a chain of various image optimization tools.
"""
alias ExOptimizer.Image
alias ExOptimizer.Optimizers.{Cwebp, Gifsicle, JpegOptim, Optipng, PngQuant, Svgo}
@default_optimizers [
JpegOptim,
PngQuant,
Optipng,
Svgo,
Gifsicle,
Cwebp
]
@doc """
Optimize a picture
Options:
* `optimizers` accepts a list of optimizers (see `ExOptimizer.Optimizer`) module names or tuples with the module name and the custom options to pass to the binary command.
"""
@spec optimize(String.t(), List.t()) ::
{:ok, Map.t()} | {:error, Map.t()} | {:error, :file_not_found}
def optimize(path, opts \\ []) do
path = Path.expand(path)
case {:regular_file, File.regular?(path)} do
{:regular_file, true} ->
image = load_image(path)
optimizers =
opts
|> Keyword.get(:optimizers, @default_optimizers)
|> find_optimizers()
res =
Enum.reduce(optimizers, %{}, fn {optimizer_module, _optimizer_options} = optimizer,
acc ->
Map.put(acc, optimizer_module, do_optimize(image, optimizer))
end)
status =
if Enum.all?(res, &is_status_ok/1),
do: :ok,
else: :error
{status, res}
{:regular_file, false} ->
{:error, :file_not_found}
end
end
@spec do_optimize(Image.t(), {atom(), list()}) :: tuple()
defp do_optimize(%Image{path: path} = image, {optimizer, optimizer_options}) do
with {:can_handle, true} <- {:can_handle, optimizer.can_handle(image)},
{:module_not_found, {:module, _module}} <-
{:module_not_found, Code.ensure_compiled(optimizer)},
{:executable_not_found, executable_path} when is_binary(executable_path) <-
{:executable_not_found, System.find_executable(optimizer.binary_name())},
args <- optimizer_options ++ optimizer.extra_args(image) ++ [path],
{:error_command, {_return, 0} = res} <-
{:error_command, System.cmd(optimizer.binary_name(), args)} do
{:ok, res}
else
{:can_handle, false} -> {:ignore, :not_applicable}
{:error_command, res} -> {:error, :command, res}
{:executable_not_found, nil} -> {:ignore, :executable_not_found}
{:module_not_found, _} -> {:error, :module_not_loaded}
end
end
@spec find_optimizers(List.t()) :: List.t()
defp find_optimizers(optimizers), do: Enum.map(optimizers, &make_optimizer_config/1)
@spec make_optimizer_config(tuple()) :: tuple()
defp make_optimizer_config({optimizer, config}), do: {optimizer, config}
@spec make_optimizer_config(atom()) :: tuple()
defp make_optimizer_config(optimizer), do: {optimizer, optimizer.options()}
@spec load_image(String.t()) :: Image.t()
defp load_image(path) do
infos = FileInfo.get_info(path)
%FileInfo.Mime{subtype: subtype, type: type} = Map.get(infos, path)
%Image{path: path, ext: Path.extname(path), mime: "#{type}/#{subtype}"}
end
defp is_status_ok({_, {status, _}}) when status in [:ok, :ignore], do: true
defp is_status_ok({_, {status, _, _}}) when status in [:ok, :ignore], do: true
defp is_status_ok(_), do: false
end