/ elixir

Generating Image Thumbnail In Elixir Phoenix With ImageMagick

Thumbnails becomes inevitable part of the process when dealing with images. They provide us a way to give a peek through the original image which helps in building rich user experience in modern applications.

In an internal productivity tool while building this experience, Our whole attachments feature was ready and we had found the only available option in Elixir/Phoenix ecosystem Thumbnex for generating thumbnails. Once we started integrating this library, we almost immediately got hit by following error:

 ** (ErlangError) Erlang error: :enoent
        (elixir) lib/system.ex:622: System.cmd("ffprobe", ["-show_format", "/var/folders/k2/37ht7xpx4g1fswv0cn003gp80000gp/T/plug-1531/multipart-739416-202247-1"], [stderr_to_stdout: true])
        lib/ffprobe.ex:47: FFprobe.format/1
        lib/thumbnex/animations.ex:6: Thumbnex.Animations.duration/1
        lib/thumbnex.ex:35: Thumbnex.create_thumbnail/3

To give some background, Thumbnex hex package uses ImageMagick and FFmpeg internally to deliver thumbnail generation & other features. ImageMagick & FFmpeg are two most popular image & multimedia manipulation command line tools, respectively.

After debugging we figured out the error is related to ffprobe which is a tool provided by FFmpeg for printing info related to media files. Since we were mostly interested in image thumbnail generation, ImageMagick is what we were focused on.

Therefore we started exploring other options to achieve our goal without adding unwanted complexity. We couldn't find any hex package which is primarily focused on image thumbnail generation. Hence, we decided will roll our own solution.

First step was to understand how to generate thumbnail using ImageMagick, Their official site has some documentation around dealing with thumbnails. After reading through we could compile a basic thumbnail generation command

convert -define jpeg:size=500x180 trackive-staging/attachments/scope.id/sample.jpeg -auto-orient    -thumbnail 250x90   -unsharp 0x.5 sample_thumb.jpeg

-thumbnail - It resizes the image and strips all the profile and comment data to make it smaller.

-define jpeg:size= - This is used for setting initial size original image which has been set twice that of final image so that it is clearly visible and doesn't look blurry. It is used to avoid operation over large input images.

-auto-orient - It is used to set the image to correct orientation. This is helpful if the image is from Cameras.

- unsharp - We can improve the above result by sharpening the image slightly using "-unsharp" after the "-thumbnail" resize operation.

Specify the required thumbnail dimension after -thumbnail.

Next step was, executing this command from Elixir/Phoenix application. Elixir's System.cmd can interact with the host system directly, meaning we can pass commands to this function and it will get executed on our machine.

Now it was just a matter of putting together these knowledge in our code. Here is the code snippet we compiled

defmodule Image.Transformer do
  def transform(original_file, operation \\ "convert") do
    #1 get thumbnail file name
    thumb_path = generate_thumb_file(original_file)
    
    #2 generate thumbnail by passing appropriate parameters
    System.cmd(operation, operation_commands(original_file_path, thumb_path))
    
    #3 return the generated thumbnail
    thumb_path
  end

  defp generate_thumb_file(original_file) do
	 thumb_file = original_file
   	   |> String.replace(".", "_thumb.jpeg")
  end

  defp operation_commands(original_file_path, thumb_path, size \\ "250x90") do
    [
      "-define",
      "jpeg:size=500x180",
      original_file_path,
      "-auto-orient",
      "-thumbnail",
      size,
      "-unsharp",
      "0x.5",
      thumb_path
    ]
  end
end

To summarize the above snippet, First, we get the original image file and generate a file name for its thumbnail. If the file name is sample.jpeg then thumbnail will be named as sample_thumb.jpeg.

Then, we execute the necessary ImageMagick command with the appropriate parameters and return the newly generated thumbnail path.

Here is the link of above code snippet https://gist.github.com/trojanh/319d8c01b3c4c139bd1df930ea1c5d48

Discussion on Reddit: https://www.reddit.com/r/elixir/comments/915inl/generating_image_thumbnail_in_elixir_phoenix_with/