aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/upload/filter/analyze_metadata.ex
blob: 7ee643277fbb4b4b1142a54a2dbb327812cccdd2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 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.AnalyzeMetadata do
  @moduledoc """
  Extracts metadata about the upload, such as width/height
  """
  require Logger

  alias Vix.Vips.Image
  alias Vix.Vips.Operation

  @behaviour Pleroma.Upload.Filter

  @spec filter(Pleroma.Upload.t()) ::
          {:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()}
  def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
    try do
      {:ok, image} = Image.new_from_file(file)
      {width, height} = {Image.width(image), Image.height(image)}

      upload =
        upload
        |> Map.put(:width, width)
        |> Map.put(:height, height)
        |> Map.put(:blurhash, get_blurhash(image))

      {:ok, :filtered, upload}
    rescue
      e in ErlangError ->
        Logger.warning("#{__MODULE__}: #{inspect(e)}")
        {:ok, :noop}
    end
  end

  def filter(%Pleroma.Upload{tempfile: file, content_type: "video" <> _} = upload) do
    try do
      result = media_dimensions(file)

      upload =
        upload
        |> Map.put(:width, result.width)
        |> Map.put(:height, result.height)

      {:ok, :filtered, upload}
    rescue
      e in ErlangError ->
        Logger.warning("#{__MODULE__}: #{inspect(e)}")
        {:ok, :noop}
    end
  end

  def filter(_), do: {:ok, :noop}

  defp get_blurhash(file) do
    with {:ok, blurhash} <- vips_blurhash(file) do
      blurhash
    else
      _ -> nil
    end
  end

  defp media_dimensions(file) do
    with executable when is_binary(executable) <- System.find_executable("ffprobe"),
         args = [
           "-v",
           "error",
           "-show_entries",
           "stream=width,height",
           "-of",
           "csv=p=0:s=x",
           file
         ],
         {result, 0} <- System.cmd(executable, args),
         [width, height] <-
           String.split(String.trim(result), "x") |> Enum.map(&String.to_integer(&1)) do
      %{width: width, height: height}
    else
      nil -> {:error, {:ffprobe, :command_not_found}}
      error -> {:error, error}
    end
  end

  defp vips_blurhash(%Vix.Vips.Image{} = image) do
    with {:ok, resized_image} <- Operation.thumbnail_image(image, 100),
         {height, width} <- {Image.height(resized_image), Image.width(resized_image)},
         max <- max(height, width),
         {x, y} <- {max(round(width * 5 / max), 1), max(round(height * 5 / max), 1)} do
      {:ok, rgb} =
        if Image.has_alpha?(resized_image) do
          # remove alpha channel
          resized_image
          |> Operation.extract_band!(0, n: 3)
          |> Image.write_to_binary()
        else
          Image.write_to_binary(resized_image)
        end

      Blurhash.encode(rgb, width, height, x, y)
    else
      _ -> nil
    end
  end
end