Serve artist images via assets
This commit is contained in:
@@ -11,7 +11,8 @@ defmodule LastFm.Artist do
|
|||||||
image: String.t(),
|
image: String.t(),
|
||||||
play_count: non_neg_integer(),
|
play_count: non_neg_integer(),
|
||||||
on_tour: boolean(),
|
on_tour: boolean(),
|
||||||
base_url: String.t()
|
base_url: String.t(),
|
||||||
|
image_data_hash: String.t() | nil
|
||||||
}
|
}
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
@@ -23,6 +24,7 @@ defmodule LastFm.Artist do
|
|||||||
field :play_count, :integer, default: 0
|
field :play_count, :integer, default: 0
|
||||||
field :on_tour, :boolean, default: false
|
field :on_tour, :boolean, default: false
|
||||||
field :base_url, :string
|
field :base_url, :string
|
||||||
|
field :image_data_hash, :string
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_api_response(api_response) do
|
def from_api_response(api_response) do
|
||||||
|
|||||||
@@ -56,6 +56,19 @@ defmodule MusicLibrary.Artists do
|
|||||||
q |> Repo.all()
|
q |> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_image_hashes(lastfm_artists) do
|
||||||
|
musicbrainz_ids = Enum.map(lastfm_artists, & &1.musicbrainz_id)
|
||||||
|
|
||||||
|
q =
|
||||||
|
from ai in ArtistInfo,
|
||||||
|
where: ai.id in ^musicbrainz_ids,
|
||||||
|
select: {ai.id, ai.image_data_hash}
|
||||||
|
|
||||||
|
q
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.into(%{})
|
||||||
|
end
|
||||||
|
|
||||||
def exists?(artist_id) do
|
def exists?(artist_id) do
|
||||||
q =
|
q =
|
||||||
from ar in ArtistRecord,
|
from ar in ArtistRecord,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ defmodule MusicLibrary.Search do
|
|||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
|
|
||||||
|
alias MusicLibrary.Artists.ArtistInfo
|
||||||
alias MusicLibrary.Records.ArtistRecord
|
alias MusicLibrary.Records.ArtistRecord
|
||||||
alias MusicLibrary.{Collection, Repo, Wishlist}
|
alias MusicLibrary.{Collection, Repo, Wishlist}
|
||||||
|
|
||||||
@@ -64,10 +65,12 @@ defmodule MusicLibrary.Search do
|
|||||||
|
|
||||||
q =
|
q =
|
||||||
from ar in ArtistRecord,
|
from ar in ArtistRecord,
|
||||||
|
join: ai in ArtistInfo,
|
||||||
|
on: ar.musicbrainz_id == ai.id,
|
||||||
where:
|
where:
|
||||||
fragment("lower(unaccent(artist ->> '$.name')) LIKE ?", ^"%#{normalized_query}%"),
|
fragment("lower(unaccent(artist ->> '$.name')) LIKE ?", ^"%#{normalized_query}%"),
|
||||||
group_by: ar.musicbrainz_id,
|
group_by: ar.musicbrainz_id,
|
||||||
select: ar.artist,
|
select: %{artist: ar.artist, image_data_hash: ai.image_data_hash},
|
||||||
limit: ^limit,
|
limit: ^limit,
|
||||||
order_by: fragment("artist ->> '$.name'")
|
order_by: fragment("artist ->> '$.name'")
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
|
|
||||||
import MusicLibraryWeb.RecordComponents, only: [format_label: 1, type_label: 1, record_cover: 1]
|
import MusicLibraryWeb.RecordComponents, only: [format_label: 1, type_label: 1, record_cover: 1]
|
||||||
|
|
||||||
|
alias MusicLibrary.Assets.Transform
|
||||||
alias MusicLibrary.Records.Record
|
alias MusicLibrary.Records.Record
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -63,6 +64,7 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
<.search_result_artist artist={artist} />
|
<.search_result_artist artist={artist} />
|
||||||
"""
|
"""
|
||||||
attr :artist, :map, required: true
|
attr :artist, :map, required: true
|
||||||
|
attr :image_data_hash, :string, required: false
|
||||||
attr :rest, :global, include: ~w(phx-click phx-value-id)
|
attr :rest, :global, include: ~w(phx-click phx-value-id)
|
||||||
|
|
||||||
def search_result_artist(assigns) do
|
def search_result_artist(assigns) do
|
||||||
@@ -78,7 +80,7 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<img
|
<img
|
||||||
class="w-12 h-12 rounded-md aspect-square object-cover"
|
class="w-12 h-12 rounded-md aspect-square object-cover"
|
||||||
src={~p"/artists/#{@artist.musicbrainz_id}/image"}
|
src={artist_image_path(@image_data_hash)}
|
||||||
alt={@artist.name}
|
alt={@artist.name}
|
||||||
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
||||||
/>
|
/>
|
||||||
@@ -96,6 +98,14 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp artist_image_path(image_data_hash) do
|
||||||
|
payload =
|
||||||
|
%Transform{hash: image_data_hash, width: 96}
|
||||||
|
|> Transform.encode!()
|
||||||
|
|
||||||
|
~p"/assets/#{payload}"
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a search result group with a title and items.
|
Renders a search result group with a title and items.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ defmodule MusicLibraryWeb.ArtistLive.FormComponent do
|
|||||||
use MusicLibraryWeb, :live_component
|
use MusicLibraryWeb, :live_component
|
||||||
|
|
||||||
alias MusicLibrary.Artists
|
alias MusicLibrary.Artists
|
||||||
|
alias MusicLibrary.Assets.Transform
|
||||||
alias Vix.Vips.Image
|
alias Vix.Vips.Image
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -46,7 +47,7 @@ defmodule MusicLibraryWeb.ArtistLive.FormComponent do
|
|||||||
:if={@uploads.image_data.entries == []}
|
:if={@uploads.image_data.entries == []}
|
||||||
class="rounded-lg mx-auto w-full"
|
class="rounded-lg mx-auto w-full"
|
||||||
alt={@artist.name}
|
alt={@artist.name}
|
||||||
src={~p"/artists/#{@artist_info.id}/image?vsn=#{@artist_info.image_data_hash || ""}"}
|
src={artist_image_path(@artist_info)}
|
||||||
/>
|
/>
|
||||||
<.live_img_preview
|
<.live_img_preview
|
||||||
:for={entry <- @uploads.image_data.entries}
|
:for={entry <- @uploads.image_data.entries}
|
||||||
@@ -154,4 +155,12 @@ defmodule MusicLibraryWeb.ArtistLive.FormComponent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||||
|
|
||||||
|
defp artist_image_path(artist_info) do
|
||||||
|
payload =
|
||||||
|
%Transform{hash: artist_info.image_hash, width: 96}
|
||||||
|
|> Transform.encode!()
|
||||||
|
|
||||||
|
~p"/assets/#{payload}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
|
|||||||
|
|
||||||
alias MusicLibrary.Artists.ArtistInfo
|
alias MusicLibrary.Artists.ArtistInfo
|
||||||
alias MusicLibrary.{Artists, Records}
|
alias MusicLibrary.{Artists, Records}
|
||||||
|
alias(MusicLibrary.Assets.Transform)
|
||||||
|
|
||||||
attr :country, :map, required: true
|
attr :country, :map, required: true
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
|
|||||||
<div class="group overflow-hidden rounded-lg bg-zinc-100 focus-within:ring-2 focus-within:ring-zinc-500 focus-within:ring-offset-2 focus-within:ring-offset-zinc-100">
|
<div class="group overflow-hidden rounded-lg bg-zinc-100 focus-within:ring-2 focus-within:ring-zinc-500 focus-within:ring-offset-2 focus-within:ring-offset-zinc-100">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img
|
<img
|
||||||
src={~p"/artists/#{artist.musicbrainz_id}/image"}
|
src={artist_thumb_path(artist)}
|
||||||
alt={artist.name}
|
alt={artist.name}
|
||||||
class="pointer-events-none aspect-square object-cover group-hover:opacity-75"
|
class="pointer-events-none aspect-square object-cover group-hover:opacity-75"
|
||||||
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
||||||
@@ -212,6 +213,13 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
|
|||||||
end)
|
end)
|
||||||
|> assign_async(:similar_artists, fn ->
|
|> assign_async(:similar_artists, fn ->
|
||||||
with {:ok, similar_artists} <- Artists.get_similar_artists(artist) do
|
with {:ok, similar_artists} <- Artists.get_similar_artists(artist) do
|
||||||
|
artist_image_hashes = Artists.get_image_hashes(similar_artists)
|
||||||
|
|
||||||
|
similar_artists =
|
||||||
|
Enum.map(similar_artists, fn artist ->
|
||||||
|
%{artist | image_data_hash: Map.get(artist_image_hashes, artist.musicbrainz_id)}
|
||||||
|
end)
|
||||||
|
|
||||||
{:ok, %{similar_artists: similar_artists}}
|
{:ok, %{similar_artists: similar_artists}}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -354,4 +362,20 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
|
|||||||
attributes: [class: "mt-2 text-sm/7"]
|
attributes: [class: "mt-2 text-sm/7"]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp artist_image_path(artist_info) do
|
||||||
|
payload =
|
||||||
|
%Transform{hash: artist_info.image_data_hash}
|
||||||
|
|> Transform.encode!()
|
||||||
|
|
||||||
|
~p"/assets/#{payload}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp artist_thumb_path(artist_info) do
|
||||||
|
payload =
|
||||||
|
%Transform{hash: artist_info.image_data_hash, width: 300}
|
||||||
|
|> Transform.encode!()
|
||||||
|
|
||||||
|
~p"/assets/#{payload}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<img
|
<img
|
||||||
class="w-full rounded-md shadow-sm mt-4"
|
class="w-full rounded-md shadow-sm mt-4"
|
||||||
src={~p"/artists/#{@artist_info.id}/image?vsn=#{@artist_info.image_data_hash || ""}"}
|
src={artist_image_path(@artist_info)}
|
||||||
alt={@artist.name}
|
alt={@artist.name}
|
||||||
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
onerror={"this.src = '" <> ~p"/images/cover-not-found.png" <> "';"}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -91,8 +91,9 @@
|
|||||||
class="border-t border-zinc-200 dark:border-zinc-700"
|
class="border-t border-zinc-200 dark:border-zinc-700"
|
||||||
>
|
>
|
||||||
<.search_result_artist
|
<.search_result_artist
|
||||||
:for={artist <- @search_results.artists}
|
:for={%{artist: artist, image_data_hash: image_data_hash} <- @search_results.artists}
|
||||||
artist={artist}
|
artist={artist}
|
||||||
|
image_data_hash={image_data_hash}
|
||||||
phx-click="navigate_to_artist"
|
phx-click="navigate_to_artist"
|
||||||
phx-value-id={artist.musicbrainz_id}
|
phx-value-id={artist.musicbrainz_id}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ defmodule MusicLibraryWeb.Router do
|
|||||||
get "/backup", ArchiveController, :backup
|
get "/backup", ArchiveController, :backup
|
||||||
|
|
||||||
get "/assets/:transform_payload", AssetController, :show
|
get "/assets/:transform_payload", AssetController, :show
|
||||||
get "/artists/:musicbrainz_id/image", ArtistController, :image
|
|
||||||
|
|
||||||
live_session :default,
|
live_session :default,
|
||||||
on_mount: [MusicLibraryWeb.Hooks.StaticAssets, MusicLibraryWeb.Hooks.GetTimezone] do
|
on_mount: [MusicLibraryWeb.Hooks.StaticAssets, MusicLibraryWeb.Hooks.GetTimezone] do
|
||||||
|
|||||||
Reference in New Issue
Block a user