Waffle.Processor (waffle v1.1.5)
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}
- Theexecutable
will be called withSystem.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 topng
), then the new file extension must be explicit.
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"}
end
end
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 apng
orjpg
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 \
"$1"
}
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}
end