Compare commits

...

2 Commits

Author SHA1 Message Date
Mark Felder 31d4448ee6 Add Pleroma.Upload.Filter.HeifToJpeg based on vips 2022-11-10 11:07:49 -05:00
Mark Felder d76fb1e6b4 Replace ImageMagick with Vips for Media Preview Proxy 2022-11-09 14:29:29 -05:00
8 changed files with 92 additions and 46 deletions

View File

@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added move account API
- Enable remote users to interact with posts
- Possibility to discover users like `user@example.org`, while Pleroma is working on `pleroma.example.org`. Additional configuration required.
- Added Pleroma.Upload.Filter.HeifToJpeg to automate converting .heic files from Apple devices to JPEGs which can be viewed in browsers.
### Fixed
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies

View File

@ -4,7 +4,7 @@ COPY . .
ENV MIX_ENV=prod
RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
RUN apk add git gcc g++ musl-dev make cmake file-dev vips &&\
echo "import Config" > config/prod.secret.exs &&\
mix local.hex --force &&\
mix local.rebar --force &&\
@ -32,7 +32,7 @@ ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma
RUN apk update &&\
apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
apk add exiftool ffmpeg vips libmagic ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\

View File

@ -8,11 +8,12 @@ defmodule Pleroma.Helpers.MediaHelper do
"""
alias Pleroma.HTTP
alias Vix.Vips.Operation
require Logger
def missing_dependencies do
Enum.reduce([imagemagick: "convert", ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc ->
Enum.reduce([ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc ->
if Pleroma.Utils.command_available?(executable) do
acc
else
@ -22,54 +23,22 @@ def missing_dependencies do
end
def image_resize(url, options) do
with executable when is_binary(executable) <- System.find_executable("convert"),
{:ok, args} <- prepare_image_resize_args(options),
{:ok, env} <- HTTP.get(url, [], pool: :media),
{:ok, fifo_path} <- mkfifo() do
args = List.flatten([fifo_path, args])
run_fifo(fifo_path, env, executable, args)
with {:ok, env} <- HTTP.get(url, [], pool: :media),
{:ok, resized} <-
Operation.thumbnail_buffer(env.body, options.max_width,
height: options.max_height,
size: :VIPS_SIZE_DOWN
) do
if options[:format] == "png" do
Operation.pngsave_buffer(resized, Q: options[:quality])
else
Operation.jpegsave_buffer(resized, Q: options[:quality], interlace: true)
end
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, format: "png"} = options
) do
quality = options[:quality] || 85
resize = Enum.join([max_width, "x", max_height, ">"])
args = [
"-resize",
resize,
"-quality",
to_string(quality),
"png:-"
]
{:ok, args}
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),
"jpg:-"
]
{:ok, args}
end
defp prepare_image_resize_args(_), do: {:error, :missing_options}
# Note: video thumbnail is intentionally not resized (always has original dimensions)
def video_framegrab(url) do
with executable when is_binary(executable) <- System.find_executable("ffmpeg"),

View File

@ -0,0 +1,36 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.HeifToJpeg do
@behaviour Pleroma.Upload.Filter
alias Pleroma.Upload
alias Vix.Vips.Operation
@type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()}
@type conversions :: conversion() | [conversion()]
@spec filter(Pleroma.Upload.t()) :: {:ok, :atom} | {:error, String.t()}
def filter(%Pleroma.Upload{content_type: "image/avif"} = upload), do: apply_filter(upload)
def filter(%Pleroma.Upload{content_type: "image/heic"} = upload), do: apply_filter(upload)
def filter(%Pleroma.Upload{content_type: "image/heif"} = upload), do: apply_filter(upload)
def filter(_), do: {:ok, :noop}
defp apply_filter(%Pleroma.Upload{name: name, path: path, tempfile: tempfile} = upload) do
ext = String.split(path, ".") |> List.last()
try do
name = name |> String.replace_suffix(ext, "jpg")
path = path |> String.replace_suffix(ext, "jpg")
{:ok, {vixdata, _vixflags}} = Operation.heifload(tempfile)
{:ok, jpegdata} = Operation.jpegsave_buffer(vixdata)
:ok = File.write(tempfile, jpegdata)
{:ok, :filtered, %Upload{upload | name: name, path: path, content_type: "image/jpeg"}}
rescue
e in ErlangError ->
{:error, "#{__MODULE__}: #{inspect(e)}"}
end
end
end

View File

@ -198,6 +198,7 @@ defp deps do
{:open_api_spex, "~> 3.10"},
{:phoenix_live_dashboard, "~> 0.6.2"},
{:ecto_psql_extras, "~> 0.6"},
{:vix, "~> 0.14.0"},
# indirect dependency version override
{:plug, "~> 1.10.4", override: true},

View File

@ -133,6 +133,7 @@
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
"vix": {:hex, :vix, "0.14.0", "f84eaf28191514d385829580db4e1c971e75ecfa771538a40159c18d88340049", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "964003b93351b51d0a5b80d80b3d568da4546e6ecbb1ee0bd0fb0a5c8d6e8cc9"},
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
}

BIN
test/fixtures/image.heic vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,38 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.HeifToJpegTest do
use Pleroma.DataCase, async: true
alias Pleroma.Upload.Filter
test "apply HeicToJpeg filter" do
File.cp!(
"test/fixtures/image.heic",
"test/fixtures/heictmp"
)
upload = %Pleroma.Upload{
name: "image.heic",
content_type: "image/heic",
path: Path.absname("test/fixtures/image.heic"),
tempfile: Path.absname("test/fixtures/heictmp")
}
{:ok, :filtered, result} = Filter.HeifToJpeg.filter(upload)
assert result.content_type == "image/jpeg"
assert result.name == "image.jpg"
assert String.ends_with?(result.path, "jpg")
assert {:ok,
%Majic.Result{
content:
"JPEG image data, JFIF standard 1.02, resolution (DPI), density 96x96, segment length 16, progressive, precision 8, 1024x768, components 3",
encoding: "binary",
mime_type: "image/jpeg"
}} == Majic.perform(result.path, pool: Pleroma.MajicPool)
on_exit(fn -> File.rm!("test/fixtures/heictmp") end)
end
end