We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
DIYTG - Do It Yourself Thumbnail Generation
I found myself needing a quick cheap origin server for a CDN that’s going to host and dynamically resize ~300gb of book covers. The server needs to take the width of the desired thumbnail and the isbn of the book, and return an image in the response.
An hours work in Elixir and I’ve got something I’m not afraid to throw in production. How cool is that?
All the code required
To generate the thumbnails on the fly we need a fast library to do it for us. Enter Vix, a great elixir extension that uses NIF to call into libvips for great performance™.
The first thing we do, lets generate yet another phoenix project.
$ mix phx.new thubz
Great project name as usual!
Then we add our one dependency
# mix.exs
defp deps do
[
...
{:vix, "0.26.0"}
]
end
Then we hijack the default page router
# router.ex
scope "/", ThubzWeb do
pipe_through :browser
get "/thumbs/:width/:isbn", PageController, :home
end
And finally we hijack the controller.
# page_controller.ex
defmodule ThubzWeb.PageController do
use ThubzWeb, :controller
alias Vix.Vips
@root_path "/ftp_sync/images/Complete/"
def home(conn, %{"isbn" => isbn, "width" => width}) do
case path_from_isbn(isbn) do
{:ok, path} ->
width = String.to_integer(width)
# From https://www.libvips.org/API/current/libvips-resample.html#vips-thumbnail
# The output image will fit within a square of size width x width.
# You can specify a separate height with the height option.
# Set either width or height to a very large number to ignore that dimension.
very_large_number = 4000
{:ok, data} =
path
|> Vips.Operation.thumbnail!(width, height: very_large_number, size: :VIPS_SIZE_DOWN)
|> Vips.Image.write_to_buffer(".jpg")
conn
|> put_resp_content_type("image/jpeg")
|> send_resp(:ok, data)
_ ->
conn |> send_resp(404, "Not found")
end
end
defp path_from_isbn(isbn) do
# The images are stored in subfolders based on the first 8 digits of the ISBN
sub_folder = isbn |> String.slice(0..7)
# And they're all jpegs
path = (Path.join([@root_path, sub_folder, isbn]) <> ".jpg") |> dbg()
# But a lot of those files don't exist
if File.exists?(path) do
{:ok, path}
else
{:error, :not_found}
end
end
end
Just setting the width for some reason set the hight, so that threw me off for a second. The libvips docs suggested setting height to a very large number, and that did fix it.
And that’s it!