Handles common media-related operations.
"""
- def ffmpeg_resize_remote(uri, %{max_width: max_width, max_height: max_height}) do
- cmd = ~s"""
- curl -L "#{uri}" |
- ffmpeg -i pipe:0 -f lavfi -i color=c=white \
- -filter_complex "[0:v] scale='min(#{max_width},iw)':'min(#{max_height},ih)': \
- force_original_aspect_ratio=decrease [scaled]; \
- [1][scaled] scale2ref [bg][img]; [bg] setsar=1 [bg]; [bg][img] overlay=shortest=1" \
- -f image2 -vcodec mjpeg -frames:v 1 pipe:1 | \
- cat
- """
-
- with {:ok, [stdout: stdout_list]} <- Pleroma.Exec.cmd(cmd) do
- {:ok, Enum.join(stdout_list)}
+ @tmp_base "/tmp/pleroma-media_preview-pipe"
+
+ def image_resize(url, options) do
+ with executable when is_binary(executable) <- System.find_executable("convert"),
+ {:ok, args} <- prepare_image_resize_args(options),
+ url = Pleroma.Web.MediaProxy.url(url),
+ {:ok, env} <- Pleroma.HTTP.get(url),
+ {:ok, fifo_path} <- mkfifo()
+ do
+ run_fifo(fifo_path, env, executable, args)
+ else
+ nil -> {:error, {:convert, :command_not_found}}
+ {:error, _} = error -> error
+ end
+ end
+
+ defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do
+ quality = options[:quality] || 85
+ resize = Enum.join([max_width, "x", max_height, ">"])
+ args = [
+ "-interlace", "Plane",
+ "-resize", resize,
+ "-quality", to_string(quality)
+ ]
+ {:ok, args}
+ end
+
+ defp prepare_image_resize_args(_), do: {:error, :missing_options}
+
+ defp run_fifo(fifo_path, env, executable, args) do
+ args = List.flatten([fifo_path, args, "jpg:-"])
+ pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args])
+ fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out])
+ true = Port.command(fifo, env.body)
+ :erlang.port_close(fifo)
+ loop_recv(pid)
+ after
+ File.rm(fifo_path)
+ end
+
+ defp mkfifo() do
+ path = "#{@tmp_base}#{to_charlist(:erlang.phash2(self()))}"
+ case System.cmd("mkfifo", [path]) do
+ {_, 0} ->
+ spawn(fifo_guard(path))
+ {:ok, path}
+ {_, err} ->
+ {:error, {:fifo_failed, err}}
+ end
+ end
+
+ defp fifo_guard(path) do
+ pid = self()
+ fn() ->
+ ref = Process.monitor(pid)
+ receive do
+ {:DOWN, ^ref, :process, ^pid, _} ->
+ File.rm(path)
+ end
+ end
+ end
+
+ defp loop_recv(pid) do
+ loop_recv(pid, <<>>)
+ end
+
+ defp loop_recv(pid, acc) do
+ receive do
+ {^pid, {:data, data}} ->
+ loop_recv(pid, acc <> data)
+ {^pid, {:exit_status, 0}} ->
+ {:ok, acc}
+ {^pid, {:exit_status, status}} ->
+ {:error, status}
+ after
+ 5000 ->
+ :erlang.port_close(pid)
+ {:error, :timeout}
end
end
end