total rebase
[anni] / lib / pleroma / upload / filter / analyze_metadata.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Upload.Filter.AnalyzeMetadata do
6   @moduledoc """
7   Extracts metadata about the upload, such as width/height
8   """
9   require Logger
10
11   alias Vix.Vips.Image
12   alias Vix.Vips.Operation
13
14   @behaviour Pleroma.Upload.Filter
15
16   @spec filter(Pleroma.Upload.t()) ::
17           {:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()}
18   def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
19     try do
20       {:ok, image} = Image.new_from_file(file)
21       {width, height} = {Image.width(image), Image.height(image)}
22
23       upload =
24         upload
25         |> Map.put(:width, width)
26         |> Map.put(:height, height)
27         |> Map.put(:blurhash, get_blurhash(image))
28
29       {:ok, :filtered, upload}
30     rescue
31       e in ErlangError ->
32         Logger.warning("#{__MODULE__}: #{inspect(e)}")
33         {:ok, :noop}
34     end
35   end
36
37   def filter(%Pleroma.Upload{tempfile: file, content_type: "video" <> _} = upload) do
38     try do
39       result = media_dimensions(file)
40
41       upload =
42         upload
43         |> Map.put(:width, result.width)
44         |> Map.put(:height, result.height)
45
46       {:ok, :filtered, upload}
47     rescue
48       e in ErlangError ->
49         Logger.warning("#{__MODULE__}: #{inspect(e)}")
50         {:ok, :noop}
51     end
52   end
53
54   def filter(_), do: {:ok, :noop}
55
56   defp get_blurhash(file) do
57     with {:ok, blurhash} <- vips_blurhash(file) do
58       blurhash
59     else
60       _ -> nil
61     end
62   end
63
64   defp media_dimensions(file) do
65     with executable when is_binary(executable) <- System.find_executable("ffprobe"),
66          args = [
67            "-v",
68            "error",
69            "-show_entries",
70            "stream=width,height",
71            "-of",
72            "csv=p=0:s=x",
73            file
74          ],
75          {result, 0} <- System.cmd(executable, args),
76          [width, height] <-
77            String.split(String.trim(result), "x") |> Enum.map(&String.to_integer(&1)) do
78       %{width: width, height: height}
79     else
80       nil -> {:error, {:ffprobe, :command_not_found}}
81       error -> {:error, error}
82     end
83   end
84
85   defp vips_blurhash(%Vix.Vips.Image{} = image) do
86     with {:ok, resized_image} <- Operation.thumbnail_image(image, 100),
87          {height, width} <- {Image.height(resized_image), Image.width(resized_image)},
88          max <- max(height, width),
89          {x, y} <- {max(round(width * 5 / max), 1), max(round(height * 5 / max), 1)} do
90       {:ok, rgb} =
91         if Image.has_alpha?(resized_image) do
92           # remove alpha channel
93           resized_image
94           |> Operation.extract_band!(0, n: 3)
95           |> Image.write_to_binary()
96         else
97           Image.write_to_binary(resized_image)
98         end
99
100       Blurhash.encode(rgb, width, height, x, y)
101     else
102       _ -> nil
103     end
104   end
105 end