Waffle.Processor (waffle v1.1.8)

Apply transformation to files.

Waffle can be used to facilitate transformations of uploaded files via any system executable. Some common operations you may want to take on uploaded files include resizing an uploaded avatar with ImageMagick or extracting a still image from a video with FFmpeg.

To transform an image, the definition module must define a transform/2 function which accepts a version atom and a tuple consisting of the uploaded file and corresponding scope.

This transform handler accepts the version atom, as well as the file/scope argument, and is responsible for returning one of the following:

  • :noaction - The original file will be stored as-is.

  • :skip - Nothing will be stored for the provided version.

  • {executable, args} - The executable will be called with System.cmd with the format #{original_file_path} #{args} #{transformed_file_path}.

  • {executable, fn(input, output) -> args end} If your executable expects arguments in a format other than the above, you may supply a function to the conversion tuple which will be invoked to generate the arguments. The arguments can be returned as a string (e.g. – " #{input} -strip -thumbnail 10x10 #{output}") or a list (e.g. – [input, "-strip", "-thumbnail", "10x10", output]) for even more control.

  • {executable, args, output_extension} - If your transformation changes the file extension (eg, converting to png), then the new file extension must be explicit.

  • fn version, file -> {:ok, file} end - Implement custom transformation as elixir function, read more about custom transformations

  • {&transform/2, fn version, file -> :png end} - A custom transformation converting a file into a different extension


ImageMagick transformations

As images are one of the most commonly uploaded filetypes, Waffle has a recommended integration with ImageMagick's convert tool for manipulation of images. Each definition module may specify as many versions as desired, along with the corresponding transformation for each version.

The expected return value of a transform function call must either be :noaction, in which case the original file will be stored as-is, :skip, in which case nothing will be stored, or {:convert, transformation} in which the original file will be processed via ImageMagick's convert tool with the corresponding transformation parameters.

The following example stores the original file, as well as a squared 100x100 thumbnail version which is stripped of comments (eg, GPS coordinates):

defmodule Avatar do
  use Waffle.Definition

  @versions [:original, :thumb]

  def transform(:thumb, _) do
    {:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100"}

Other examples:

# Change the file extension through ImageMagick's `format` parameter:
{:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png}

# Take the first frame of a gif and process it into a square jpg:
{:convert, fn(input, output) -> "#{input}[0] -strip -thumbnail 100x100^ -gravity center -extent 100x100 -format jpg #{output}", :jpg}

For more information on defining your transformation, please consult ImageMagick's convert documentation.

Note: Keep this transformation function simple and deterministic based on the version, file name, and scope object. The transform function is subsequently called during URL generation, and the transformation is scanned for the output file format. As such, if you conditionally format the image as a png or jpg depending on the time of day, you will be displeased with the result of Waffle's URL generation.

System Resources: If you are accepting arbitrary uploads on a public site, it may be prudent to add system resource limits to prevent overloading your system resources from malicious or nefarious files. Since all processing is done directly in ImageMagick, you may pass in system resource restrictions through the -limit flag. One such example might be: -limit area 10MB -limit disk 100MB.


FFmpeg transformations

Common transformations of uploaded videos can be also defined through your definition module:

# To take a thumbnail from a video:
{:ffmpeg, fn(input, output) -> "-i #{input} -f jpg #{output}" end, :jpg}

# To convert a video to an animated gif
{:ffmpeg, fn(input, output) -> "-i #{input} -f gif #{output}" end, :gif}


Complex Transformations

Waffle requires the output of your transformation to be located at a predetermined path. However, the transformation may be done completely outside of Waffle. For fine-grained transformations, you should create an executable wrapper in your $PATH (eg. bash script) which takes these proper arguments, runs your transformation, and then moves the file into the correct location.

For example, to use soffice to convert a doc to an html file, you should place the following bash script in your $PATH:

#!/usr/bin/env sh

# `soffice` doesn't allow for output file path option, and waffle can't find the
# temporary file to process and copy. This script has a similar argument list as
# what waffle expects. See https://github.com/stavro/arc/issues/77.

set -e
set -o pipefail

function convert {
    soffice \
        --headless \
        --convert-to html \
        --outdir $TMPDIR \

function filter_new_file_name {
    awk -F$TMPDIR '{print $2}' \
    | awk -F" " '{print $1}' \
    | awk -F/ '{print $2}'

converted_file_name=$(convert "$1" | filter_new_file_name)

cp $TMPDIR/$converted_file_name "$2"
rm $TMPDIR/$converted_file_name

And perform the transformation as such:

def transform(:html, _) do
  {:soffice_wrapper, fn(input, output) -> [input, output] end, :html}

Link to this section Summary

Link to this section Functions

Link to this function

process(definition, version, arg)