Merge pull request 'Uploads: replace exiftool with the built-in exif_stripper' (#8028) from lambadalambda/pleroma:feature/exif-stripper into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/8028
This commit is contained in:
lain 2026-10-31 07:48:00 +00:00
commit 3e9dc477c9
44 changed files with 518 additions and 381 deletions

View file

@ -168,8 +168,6 @@ defmodule Pleroma.ApplicationRequirements do
defp check_system_commands!(:ok) do
filter_commands_statuses = [
check_filter(Pleroma.Upload.Filter.Exiftool.StripLocation, "exiftool"),
check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe")

View file

@ -22,40 +22,44 @@ defmodule Pleroma.Config.DeprecationWarnings do
@logger_formatter_config_knobs [:colors, :format, :metadata, :truncate, :utc_log]
@exiftool_filter_replacements %{
Pleroma.Upload.Filter.Exiftool => Pleroma.Upload.Filter.Exif.StripLocation,
Pleroma.Upload.Filter.Exiftool.StripLocation => Pleroma.Upload.Filter.Exif.StripLocation,
Pleroma.Upload.Filter.Exiftool.ReadDescription => Pleroma.Upload.Filter.Exif.ReadDescription
}
def check_exiftool_filter do
filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
if Pleroma.Upload.Filter.Exiftool in filters do
Logger.warning("""
!!!DEPRECATION WARNING!!!
Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
deprecated_filters =
filters
|> Enum.filter(&Map.has_key?(@exiftool_filter_replacements, &1))
|> Enum.uniq()
```
config :pleroma, Pleroma.Upload,
filters: [Pleroma.Upload.Filter.Exiftool]
```
case deprecated_filters do
[] ->
:ok
Is now
deprecated_filters ->
replacements =
Enum.map_join(deprecated_filters, fn filter ->
"\n* `#{inspect(filter)}` is now `#{inspect(@exiftool_filter_replacements[filter])}`"
end)
Logger.warning("""
!!!DEPRECATION WARNING!!!
Your config is using Exiftool upload filters. Pleroma no longer needs exiftool, the filters were replaced by built-in ones. The old names should work for now, but you are advised to change to the new configuration to prevent possible issues later:
#{replacements}
""")
```
config :pleroma, Pleroma.Upload,
filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
```
""")
new_config =
filters
|> Enum.map(&Map.get(@exiftool_filter_replacements, &1, &1))
|> Enum.uniq()
new_config =
filters
|> Enum.map(fn
Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripLocation
filter -> filter
end)
Config.put([Pleroma.Upload, :filters], new_config)
Config.put([Pleroma.Upload, :filters], new_config)
:error
else
:ok
:error
end
end

View file

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exif.ReadDescription do
@moduledoc """
Reads a description from image metadata when the upload has none yet.
Checks EXIF ImageDescription, then IPTC Caption-Abstract (and XMP for GIF). Blank
values and values longer than `[:instance, :description_limit]` are ignored.
"""
@behaviour Pleroma.Upload.Filter
@impl true
def filter(%Pleroma.Upload{description: description}) when is_binary(description),
do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
max_length = Pleroma.Config.get([:instance, :description_limit])
case ExifStripper.Description.read_file(file, max_length: max_length) do
description when is_binary(description) ->
{:ok, :filtered, %{upload | description: description}}
nil ->
{:ok, :noop}
end
end
def filter(_), do: {:ok, :noop}
end

View file

@ -0,0 +1,25 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exif.StripLocation do
@moduledoc """
Strips GPS metadata from uploaded images and overwrites the file in place.
Supports JPEG, PNG, WebP, TIFF and ISO-BMFF images (HEIC, AVIF). This is lossless:
the image data is not re-encoded. Other image types are left untouched.
"""
@behaviour Pleroma.Upload.Filter
@impl true
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
case ExifStripper.GpsStripper.strip_gps_data(file, file) do
:ok -> {:ok, :filtered}
:noop -> {:ok, :noop}
{:error, _} = error -> error
end
end
def filter(_), do: {:ok, :noop}
end

View file

@ -0,0 +1,14 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool do
@moduledoc """
Deprecated: use `Pleroma.Upload.Filter.Exif.StripLocation`. This module delegates to it.
"""
@behaviour Pleroma.Upload.Filter
@impl true
defdelegate filter(upload), to: Pleroma.Upload.Filter.Exif.StripLocation
end

View file

@ -4,51 +4,11 @@
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do
@moduledoc """
Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
Deprecated: use `Pleroma.Upload.Filter.Exif.ReadDescription`. This module delegates to it.
"""
@behaviour Pleroma.Upload.Filter
def filter(%Pleroma.Upload{description: description})
when is_binary(description),
do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file} = upload),
do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
def filter(_, _), do: {:ok, :noop}
defp read_description_from_exif_data(file) do
nil
|> read_when_empty(file, "-ImageDescription")
|> read_when_empty(file, "-iptc:Caption-Abstract")
end
defp read_when_empty(current_description, _, _) when is_binary(current_description),
do: current_description
defp read_when_empty(_, file, tag) do
if File.exists?(file) do
try do
{tag_content, 0} =
System.cmd("exiftool", ["-m", "-b", "-s3", tag, file],
stderr_to_stdout: false,
parallelism: true
)
tag_content = String.trim(tag_content)
if tag_content != "" and
String.length(tag_content) <=
Pleroma.Config.get([:instance, :description_limit]),
do: tag_content,
else: nil
rescue
_ in ErlangError -> nil
end
else
nil
end
end
@impl true
defdelegate filter(upload), to: Pleroma.Upload.Filter.Exif.ReadDescription
end

View file

@ -4,30 +4,11 @@
defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do
@moduledoc """
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
Deprecated: use `Pleroma.Upload.Filter.Exif.StripLocation`. This module delegates to it.
"""
@behaviour Pleroma.Upload.Filter
# Formats not compatible with exiftool at this time
def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
def filter(%Pleroma.Upload{content_type: "image/svg" <> _}), do: {:ok, :noop}
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
try do
case System.cmd("exiftool", ["-m", "-overwrite_original", "-gps:all=", "-png:all=", file],
stderr_to_stdout: true,
parallelism: true
) do
{_response, 0} -> {:ok, :filtered}
{error, _} -> {:error, error}
end
rescue
e in ErlangError ->
{:error, "#{__MODULE__}: #{inspect(e)}"}
end
end
def filter(_), do: {:ok, :noop}
@impl true
defdelegate filter(upload), to: Pleroma.Upload.Filter.Exif.StripLocation
end