Add blurhash support to backend
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -352,18 +352,14 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
||||
end
|
||||
|
||||
def make_media_data(media) when is_map(media) do
|
||||
with {:ok, %{"url" => [%{"href" => url, "mediaType" => content_type}], "size" => size}} <-
|
||||
with {:ok, %{url: url} = uploaded} <-
|
||||
Mobilizon.Web.Upload.store(media.file),
|
||||
{:media_exists, nil} <- {:media_exists, Mobilizon.Medias.get_media_by_url(url)},
|
||||
{:ok, %Media{file: _file} = media} <-
|
||||
Mobilizon.Medias.create_media(%{
|
||||
"file" => %{
|
||||
"url" => url,
|
||||
"name" => media.name,
|
||||
"content_type" => content_type,
|
||||
"size" => size
|
||||
},
|
||||
"actor_id" => media.actor_id
|
||||
file: Map.take(uploaded, [:url, :name, :content_type, :size]),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash]),
|
||||
actor_id: media.actor_id
|
||||
}) do
|
||||
Converter.Media.model_to_as(media)
|
||||
else
|
||||
|
||||
@@ -143,7 +143,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
||||
when code in 200..299 <- RemoteMediaDownloaderClient.get(url),
|
||||
name <- name || Parser.get_filename_from_response(response_headers, url) || default_name,
|
||||
{:ok, file} <- Upload.store(%{body: body, name: name}) do
|
||||
file
|
||||
Map.take(file, [:content_type, :name, :url, :size])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,17 +40,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Media do
|
||||
)
|
||||
when is_binary(media_url) do
|
||||
with {:ok, %{body: body}} <- Tesla.get(media_url, opts: @http_options),
|
||||
{:ok, %{name: name, url: url, content_type: content_type, size: size}} <-
|
||||
{:ok, %{url: url} = uploaded} <-
|
||||
Upload.store(%{body: body, name: name}),
|
||||
{:media_exists, nil} <- {:media_exists, Medias.get_media_by_url(url)} do
|
||||
Medias.create_media(%{
|
||||
"file" => %{
|
||||
"url" => url,
|
||||
"name" => name,
|
||||
"content_type" => content_type,
|
||||
"size" => size
|
||||
},
|
||||
"actor_id" => actor_id
|
||||
file: Map.take(uploaded, [:url, :name, :content_type, :size]),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash]),
|
||||
actor_id: actor_id
|
||||
})
|
||||
else
|
||||
{:media_exists, %MediaModel{file: _file} = media} ->
|
||||
|
||||
@@ -52,13 +52,16 @@ defmodule Mobilizon.GraphQL.API.Events do
|
||||
defp process_picture(%{media_id: _picture_id} = args, _), do: args
|
||||
|
||||
defp process_picture(%{media: media}, %Actor{id: actor_id}) do
|
||||
%{
|
||||
file:
|
||||
media
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_media_data(description: Map.get(media, :name)),
|
||||
actor_id: actor_id
|
||||
}
|
||||
with uploaded when is_map(uploaded) <-
|
||||
media
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_media_data(description: Map.get(media, :name)) do
|
||||
%{
|
||||
file: Map.take(uploaded, [:url, :name, :content_type, :size]),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash]),
|
||||
actor_id: actor_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@spec extract_pictures_from_event_body(map(), Actor.t()) :: map()
|
||||
|
||||
@@ -47,7 +47,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||
%{context: %{current_user: %User{} = user}}
|
||||
) do
|
||||
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
|
||||
{:ok, %{name: _name, url: url, content_type: content_type, size: size}} <-
|
||||
{:ok,
|
||||
%{
|
||||
name: _name,
|
||||
url: url,
|
||||
content_type: content_type,
|
||||
size: size
|
||||
} = uploaded} <-
|
||||
Mobilizon.Web.Upload.store(file),
|
||||
args <-
|
||||
args
|
||||
@@ -55,7 +61,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||
|> Map.put(:size, size)
|
||||
|> Map.put(:content_type, content_type),
|
||||
{:ok, media = %Media{}} <-
|
||||
Medias.create_media(%{"file" => args, "actor_id" => actor_id}) do
|
||||
Medias.create_media(%{
|
||||
file: args,
|
||||
actor_id: actor_id,
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash])
|
||||
}) do
|
||||
{:ok, transform_media(media)}
|
||||
else
|
||||
{:error, :mime_type_not_allowed} ->
|
||||
@@ -124,13 +134,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||
def user_size(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
||||
|
||||
@spec transform_media(Media.t()) :: map()
|
||||
defp transform_media(%Media{id: id, file: file}) do
|
||||
defp transform_media(%Media{id: id, file: file, metadata: metadata}) do
|
||||
%{
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
id: id,
|
||||
content_type: file.content_type,
|
||||
size: file.size
|
||||
size: file.size,
|
||||
metadata: metadata
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -215,13 +215,16 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
||||
defp process_picture(%{media_id: _picture_id} = args, _), do: args
|
||||
|
||||
defp process_picture(%{media: media}, %Actor{id: actor_id}) do
|
||||
%{
|
||||
file:
|
||||
media
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_media_data(description: Map.get(media, :name)),
|
||||
actor_id: actor_id
|
||||
}
|
||||
with uploaded when is_map(uploaded) <-
|
||||
media
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_media_data(description: Map.get(media, :name)) do
|
||||
%{
|
||||
file: Map.take(uploaded, [:url, :name, :content_type, :size]),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash]),
|
||||
actor_id: actor_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@spec extract_pictures_from_post_body(map(), String.t()) :: map()
|
||||
|
||||
@@ -14,6 +14,7 @@ defmodule Mobilizon.GraphQL.Schema.MediaType do
|
||||
field(:url, :string, description: "The media's full URL")
|
||||
field(:content_type, :string, description: "The media's detected content type")
|
||||
field(:size, :integer, description: "The media's size")
|
||||
field(:metadata, :media_metadata, description: "The media's metadata")
|
||||
end
|
||||
|
||||
@desc """
|
||||
@@ -24,6 +25,15 @@ defmodule Mobilizon.GraphQL.Schema.MediaType do
|
||||
field(:total, :integer, description: "The total number of medias in the list")
|
||||
end
|
||||
|
||||
@desc """
|
||||
Some metadata associated with a media
|
||||
"""
|
||||
object :media_metadata do
|
||||
field(:width, :integer, description: "The media width (if a picture)")
|
||||
field(:height, :integer, description: "The media width (if a height)")
|
||||
field(:blurhash, :string, description: "The media blurhash (if a picture")
|
||||
end
|
||||
|
||||
@desc "An attached media or a link to a media"
|
||||
input_object :media_input do
|
||||
# Either a full media object
|
||||
|
||||
@@ -80,11 +80,12 @@ defmodule Mobilizon.Discussions do
|
||||
# However, it also excludes all top-level comments with deleted replies from being selected
|
||||
# |> where([_, r], is_nil(r.deleted_at))
|
||||
|> group_by([c], c.id)
|
||||
|> order_by([c], desc: :is_announcement, asc: :published_at)
|
||||
|> select([c, r], %{c | total_replies: count(r.id)})
|
||||
end
|
||||
|
||||
def query(Comment, _) do
|
||||
order_by(Comment, [c], asc: :published_at)
|
||||
order_by(Comment, [c], asc: :is_announcement, asc: :published_at)
|
||||
end
|
||||
|
||||
def query(queryable, _) do
|
||||
|
||||
@@ -256,7 +256,7 @@ defmodule Mobilizon.Events.Event do
|
||||
|
||||
# In case it's a new picture
|
||||
defp put_picture(%Changeset{} = changeset, _attrs) do
|
||||
cast_assoc(changeset, :picture)
|
||||
cast_assoc(changeset, :picture, with: &Media.changeset/2)
|
||||
end
|
||||
|
||||
# Created or updated with draft parameter: don't publish
|
||||
|
||||
@@ -5,21 +5,32 @@ defmodule Mobilizon.Medias.Media do
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset, only: [cast: 3, cast_embed: 2]
|
||||
import Ecto.Changeset, only: [cast: 3, cast_embed: 2, cast_embed: 3]
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Medias.File
|
||||
alias Mobilizon.Medias.Media.Metadata
|
||||
alias Mobilizon.Posts.Post
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
file: File.t(),
|
||||
metadata: Metadata.t(),
|
||||
actor: Actor.t()
|
||||
}
|
||||
|
||||
@metadata_attrs [:height, :width, :blurhash]
|
||||
|
||||
schema "medias" do
|
||||
embeds_one(:file, File, on_replace: :update)
|
||||
|
||||
embeds_one :metadata, Metadata, on_replace: :update do
|
||||
field(:height, :integer)
|
||||
field(:width, :integer)
|
||||
field(:blurhash, :string)
|
||||
end
|
||||
|
||||
belongs_to(:actor, Actor)
|
||||
has_many(:event_picture, Event, foreign_key: :picture_id)
|
||||
many_to_many(:events, Event, join_through: "events_medias")
|
||||
@@ -36,5 +47,13 @@ defmodule Mobilizon.Medias.Media do
|
||||
media
|
||||
|> cast(attrs, [:actor_id])
|
||||
|> cast_embed(:file)
|
||||
|> cast_embed(:metadata, with: &metadata_changeset/2)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec changeset(struct(), map) :: Ecto.Changeset.t()
|
||||
def metadata_changeset(metadata, attrs) do
|
||||
metadata
|
||||
|> cast(attrs, @metadata_attrs)
|
||||
end
|
||||
end
|
||||
|
||||
47
lib/web/upload/filter/analyze_metadata.ex
Normal file
47
lib/web/upload/filter/analyze_metadata.ex
Normal file
@@ -0,0 +1,47 @@
|
||||
# Portions of this file are derived from Pleroma:
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Upstream: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/lib/pleroma/upload/filter/analyze_metadata.ex
|
||||
|
||||
defmodule Mobilizon.Web.Upload.Filter.AnalyzeMetadata do
|
||||
@moduledoc """
|
||||
Extracts metadata about the upload, such as width/height
|
||||
"""
|
||||
require Logger
|
||||
alias Mobilizon.Web.Upload
|
||||
|
||||
@behaviour Mobilizon.Web.Upload.Filter
|
||||
|
||||
@spec filter(Upload.t()) ::
|
||||
{:ok, :filtered, Upload.t()} | {:ok, :noop} | {:error, String.t()}
|
||||
def filter(%Upload{tempfile: file, content_type: "image" <> _} = upload) do
|
||||
image =
|
||||
file
|
||||
|> Mogrify.open()
|
||||
|> Mogrify.verbose()
|
||||
|
||||
upload =
|
||||
upload
|
||||
|> Map.put(:width, image.width)
|
||||
|> Map.put(:height, image.height)
|
||||
|> Map.put(:blurhash, get_blurhash(file))
|
||||
|
||||
{:ok, :filtered, upload}
|
||||
rescue
|
||||
e in ErlangError ->
|
||||
Logger.warn("#{__MODULE__}: #{inspect(e)}")
|
||||
{:ok, :noop}
|
||||
end
|
||||
|
||||
def filter(_), do: {:ok, :noop}
|
||||
|
||||
defp get_blurhash(file) do
|
||||
case :eblurhash.magick(to_charlist(file)) do
|
||||
{:ok, blurhash} ->
|
||||
to_string(blurhash)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -73,12 +73,9 @@ defmodule Mobilizon.Web.Upload do
|
||||
{:ok, upload} <- Filter.filter(opts.filters, upload),
|
||||
{:ok, url_spec} <- Uploader.put_file(opts.uploader, upload) do
|
||||
{:ok,
|
||||
%{
|
||||
name: Map.get(opts, :description) || upload.name,
|
||||
url: url_from_spec(upload, opts.base_url, url_spec),
|
||||
content_type: upload.content_type,
|
||||
size: upload.size
|
||||
}}
|
||||
upload
|
||||
|> Map.put(:name, Map.get(opts, :description) || upload.name)
|
||||
|> Map.put(:url, url_from_spec(upload, opts.base_url, url_spec))}
|
||||
else
|
||||
{:error, error} ->
|
||||
Logger.error(
|
||||
|
||||
Reference in New Issue
Block a user