Detect ImageMagick and add to Docker images

This commit is contained in:
Alex Lion
2026-06-26 11:20:39 +02:00
parent aad838b154
commit 817aa22c06
4 changed files with 121 additions and 7 deletions

View File

@@ -64,7 +64,7 @@ RUN mix release
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}
RUN apt-get update -y && apt-get install -y curl libstdc++6 openssl locales ghostscript default-jre libreoffice-java-common \
RUN apt-get update -y && apt-get install -y curl libstdc++6 openssl locales ghostscript imagemagick default-jre libreoffice-java-common \
&& apt-get install -y libreoffice --no-install-recommends && apt-get clean && rm -f /var/lib/apt/lists/*_*
# RUN apk add --no-cache curl libstdc++ openssl ncurses ghostscript openjdk11-jre

View File

@@ -37,6 +37,7 @@ RUN apt-get update && apt-get install -y \
libtool \
openjdk-11-jdk \
ghostscript \
imagemagick \
libreoffice \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -8,6 +8,8 @@ defmodule Claper.Tasks.Converter do
alias Claper.Presentations.PresentationFile
alias Porcelain.Result
@imagemagick_commands ~w(magick convert convert-im6.q16)
@doc """
Convert the presentation file to images.
We use original hash :erlang.phash2(code-name) where the files are uploaded to send it to another folder with a new hash. This last stored in db.
@@ -157,17 +159,14 @@ defmodule Claper.Tasks.Converter do
files = Path.wildcard("#{path}/*.jpg")
if Enum.empty?(files) do
{:error, :missing_slides}
else
with [_ | _] <- files,
{:ok, imagemagick_command} <- get_imagemagick_command() do
result =
Enum.reduce_while(files, :ok, fn file, _acc ->
thumb_path = Path.join(thumbs_dir, Path.basename(file))
# Generate thumbnail with 200px width, maintaining aspect ratio
# Using "magick" for ImageMagick v7+ compatibility
case Porcelain.exec(
"magick",
imagemagick_command,
[
file,
"-resize",
@@ -187,6 +186,16 @@ defmodule Claper.Tasks.Converter do
end
result
else
[] -> {:error, :missing_slides}
{:error, _reason} = error -> error
end
end
defp get_imagemagick_command do
case Enum.find(@imagemagick_commands, &System.find_executable/1) do
nil -> {:error, :missing_imagemagick}
command -> {:ok, command}
end
end

View File

@@ -0,0 +1,104 @@
defmodule Claper.Tasks.ConverterTest do
use ExUnit.Case, async: false
import ExUnit.CaptureIO
alias Claper.Presentations.PresentationFile
alias Claper.Tasks.Converter
describe "regenerate_thumbnails/1" do
test "uses convert when magick is unavailable" do
storage_dir = unique_tmp_dir()
hash = "convert-fallback"
slide_dir = Path.join([storage_dir, "uploads", hash])
bin_dir = Path.join(storage_dir, "bin")
File.mkdir_p!(slide_dir)
File.write!(Path.join(slide_dir, "1.jpg"), "slide")
File.mkdir_p!(bin_dir)
write_fake_imagemagick(bin_dir, "convert")
put_local_storage_config(storage_dir)
put_path(bin_dir)
presentation_file = %PresentationFile{hash: hash, length: 1}
output =
capture_io(fn -> assert :ok = Converter.regenerate_thumbnails(presentation_file) end)
assert output =~ "Generated 1 thumbnails"
assert File.read!(Path.join([slide_dir, "thumbs", "1.jpg"])) == "fake-thumbnail"
end
test "returns a clear error when ImageMagick is unavailable" do
storage_dir = unique_tmp_dir()
hash = "missing-imagemagick"
slide_dir = Path.join([storage_dir, "uploads", hash])
empty_bin_dir = Path.join(storage_dir, "empty-bin")
File.mkdir_p!(slide_dir)
File.write!(Path.join(slide_dir, "1.jpg"), "slide")
File.mkdir_p!(empty_bin_dir)
put_local_storage_config(storage_dir)
put_path(empty_bin_dir)
assert {:error, :missing_imagemagick} =
Converter.regenerate_thumbnails(%PresentationFile{hash: hash, length: 1})
end
end
defp write_fake_imagemagick(bin_dir, command) do
path = Path.join(bin_dir, command)
File.write!(path, """
#!/bin/sh
last=""
for arg in "$@"; do
last="$arg"
done
printf fake-thumbnail > "$last"
""")
File.chmod!(path, 0o755)
end
defp put_local_storage_config(storage_dir) do
previous_storage_dir = Application.get_env(:claper, :storage_dir)
previous_presentations = Application.get_env(:claper, :presentations)
Application.put_env(:claper, :storage_dir, storage_dir)
Application.put_env(
:claper,
:presentations,
Keyword.merge(previous_presentations || [], storage: "local")
)
on_exit(fn ->
File.rm_rf!(storage_dir)
Application.put_env(:claper, :storage_dir, previous_storage_dir)
Application.put_env(:claper, :presentations, previous_presentations)
end)
end
defp put_path(path) do
previous_path = System.get_env("PATH")
System.put_env("PATH", path)
on_exit(fn ->
if previous_path do
System.put_env("PATH", previous_path)
else
System.delete_env("PATH")
end
end)
end
defp unique_tmp_dir do
Path.join(System.tmp_dir!(), "claper-converter-test-#{System.unique_integer([:positive])}")
end
end