Rename image* to cover*
This commit is contained in:
@@ -4,7 +4,7 @@ defmodule MusicLibrary.Records do
|
|||||||
|
|
||||||
alias MusicLibrary.Records.{MusicBrainz, Record}
|
alias MusicLibrary.Records.{MusicBrainz, Record}
|
||||||
|
|
||||||
@fields [:id, :type, :format, :title, :release, :genres, :musicbrainz_id, :image_data_hash]
|
@fields [:id, :type, :format, :title, :release, :genres, :musicbrainz_id, :cover_hash]
|
||||||
|
|
||||||
def list_records(opts \\ []) do
|
def list_records(opts \\ []) do
|
||||||
limit = Keyword.get(opts, :limit, 20)
|
limit = Keyword.get(opts, :limit, 20)
|
||||||
@@ -59,11 +59,11 @@ defmodule MusicLibrary.Records do
|
|||||||
Repo.one!(q)
|
Repo.one!(q)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_image!(id) do
|
def get_cover!(id) do
|
||||||
q =
|
q =
|
||||||
from r in Record,
|
from r in Record,
|
||||||
where: r.id == ^id,
|
where: r.id == ^id,
|
||||||
select: %{image_data: r.image_data, image_data_hash: r.image_data_hash}
|
select: %{cover_data: r.cover_data, cover_hash: r.cover_hash}
|
||||||
|
|
||||||
Repo.one!(q)
|
Repo.one!(q)
|
||||||
end
|
end
|
||||||
@@ -77,15 +77,15 @@ defmodule MusicLibrary.Records do
|
|||||||
def import_from_musicbrainz(musicbrainz_id, opts \\ []) do
|
def import_from_musicbrainz(musicbrainz_id, opts \\ []) do
|
||||||
with format = Keyword.get(opts, :format, "cd"),
|
with format = Keyword.get(opts, :format, "cd"),
|
||||||
{:ok, release_group} <- MusicBrainz.get_release_group(musicbrainz_id),
|
{:ok, release_group} <- MusicBrainz.get_release_group(musicbrainz_id),
|
||||||
{:ok, image_data} <- MusicBrainz.get_cover_art(musicbrainz_id),
|
{:ok, cover_data} <- MusicBrainz.get_cover_art(musicbrainz_id),
|
||||||
record_params = build_record_params(release_group, image_data, format) do
|
record_params = build_record_params(release_group, cover_data, format) do
|
||||||
create_record(record_params)
|
create_record(record_params)
|
||||||
else
|
else
|
||||||
error -> error
|
error -> error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_record_params(release_group, image_data, format) do
|
defp build_record_params(release_group, cover_data, format) do
|
||||||
musicbrainz_id = release_group["id"]
|
musicbrainz_id = release_group["id"]
|
||||||
|
|
||||||
artists_attrs =
|
artists_attrs =
|
||||||
@@ -108,8 +108,8 @@ defmodule MusicLibrary.Records do
|
|||||||
"type" => parse_subtype(release_group["primary-type"]),
|
"type" => parse_subtype(release_group["primary-type"]),
|
||||||
"format" => format,
|
"format" => format,
|
||||||
"genres" => Enum.map(release_group["genres"], fn g -> g["name"] end),
|
"genres" => Enum.map(release_group["genres"], fn g -> g["name"] end),
|
||||||
"image_url" => "https://coverartarchive.org/release-group/#{musicbrainz_id}/front",
|
"cover_url" => "https://coverartarchive.org/release-group/#{musicbrainz_id}/front",
|
||||||
"image_data" => image_data
|
"cover_data" => cover_data
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -81,50 +81,50 @@ defmodule MusicLibrary.Records.Importer do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Pull the cover image from the stored url and keep a local, resized copy in
|
Pull the cover from the stored url and keep a local, resized copy in
|
||||||
the database for fast access/use.
|
the database for fast access/use.
|
||||||
"""
|
"""
|
||||||
def import_cover_image(record) do
|
def import_cover(record) do
|
||||||
with {:ok, image_data} <- blob_get(record.image_url) do
|
with {:ok, cover_data} <- blob_get(record.cover_url) do
|
||||||
{:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(image_data, 400)
|
{:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(cover_data, 400)
|
||||||
{:ok, thumb_data} = Vix.Vips.Image.write_to_buffer(thumb, ".jpg")
|
{:ok, thumb_data} = Vix.Vips.Image.write_to_buffer(thumb, ".jpg")
|
||||||
|
|
||||||
record
|
record
|
||||||
|> Rec.add_image_data(thumb_data)
|
|> Rec.add_cover_data(thumb_data)
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Given an already stored image in the database, resize it to a 400px wide thumbnail.
|
Given an already stored cover, resize it to a 400px wide thumbnail.
|
||||||
"""
|
"""
|
||||||
def resize_cover_image(record) do
|
def resize_cover(record) do
|
||||||
{:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(record.image_data, 400)
|
{:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(record.cover_data, 400)
|
||||||
{:ok, thumb_data} = Vix.Vips.Image.write_to_buffer(thumb, ".jpg")
|
{:ok, thumb_data} = Vix.Vips.Image.write_to_buffer(thumb, ".jpg")
|
||||||
|
|
||||||
record
|
record
|
||||||
|> Rec.add_image_data(thumb_data)
|
|> Rec.add_cover_data(thumb_data)
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def import_all_cover_images do
|
def import_all_covers do
|
||||||
Rec
|
Rec
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.each(fn r ->
|
|> Enum.each(fn r ->
|
||||||
if r.image_data == nil do
|
if r.cover_data == nil do
|
||||||
import_cover_image(r)
|
import_cover(r)
|
||||||
IO.puts("Imported cover image for #{r.title}")
|
IO.puts("Imported cover for #{r.title}")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def resize_all_cover_images do
|
def resize_all_covers do
|
||||||
Rec
|
Rec
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.each(fn r ->
|
|> Enum.each(fn r ->
|
||||||
if r.image_data != nil do
|
if r.cover_data != nil do
|
||||||
resize_cover_image(r)
|
resize_cover(r)
|
||||||
IO.puts("Resized cover image for #{r.title}")
|
IO.puts("Resized cover for #{r.title}")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -133,16 +133,16 @@ defmodule MusicLibrary.Records.Importer do
|
|||||||
Rec
|
Rec
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.each(fn r ->
|
|> Enum.each(fn r ->
|
||||||
if r.image_data != nil do
|
if r.cover_data != nil do
|
||||||
generate_cover_image_hash(r)
|
generate_cover_hash(r)
|
||||||
IO.puts("Generated cover image hash for #{r.title}")
|
IO.puts("Generated cover hash for #{r.title}")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_cover_image_hash(record) do
|
def generate_cover_hash(record) do
|
||||||
record
|
record
|
||||||
|> Rec.generate_image_data_hash()
|
|> Rec.generate_cover_hash()
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -182,8 +182,8 @@ defmodule MusicLibrary.Records.MusicBrainz do
|
|||||||
def get_cover_art(musicbrainz_id) do
|
def get_cover_art(musicbrainz_id) do
|
||||||
url = "https://coverartarchive.org/release-group/#{musicbrainz_id}/front"
|
url = "https://coverartarchive.org/release-group/#{musicbrainz_id}/front"
|
||||||
|
|
||||||
with {:ok, image_data} <- blob_get(url),
|
with {:ok, cover_data} <- blob_get(url),
|
||||||
{:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(image_data, 400) do
|
{:ok, thumb} = Vix.Vips.Operation.thumbnail_buffer(cover_data, 400) do
|
||||||
Vix.Vips.Image.write_to_buffer(thumb, ".jpg")
|
Vix.Vips.Image.write_to_buffer(thumb, ".jpg")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ defmodule MusicLibrary.Records.Record do
|
|||||||
field :type, Ecto.Enum, values: [:album, :ep, :live, :compilation, :single, :other]
|
field :type, Ecto.Enum, values: [:album, :ep, :live, :compilation, :single, :other]
|
||||||
field :format, Ecto.Enum, values: @formats
|
field :format, Ecto.Enum, values: @formats
|
||||||
field :title, :string
|
field :title, :string
|
||||||
field :image_url, :string
|
field :cover_url, :string
|
||||||
field :image_data, :binary
|
field :cover_data, :binary
|
||||||
field :image_data_hash, :string
|
field :cover_hash, :string
|
||||||
field :musicbrainz_id, Ecto.UUID
|
field :musicbrainz_id, Ecto.UUID
|
||||||
field :genres, {:array, :string}
|
field :genres, {:array, :string}
|
||||||
field :release, :string
|
field :release, :string
|
||||||
@@ -37,11 +37,11 @@ defmodule MusicLibrary.Records.Record do
|
|||||||
:musicbrainz_id,
|
:musicbrainz_id,
|
||||||
:release,
|
:release,
|
||||||
:genres,
|
:genres,
|
||||||
:image_url,
|
:cover_url,
|
||||||
:image_data
|
:cover_data
|
||||||
])
|
])
|
||||||
|> cast_embed(:artists, with: &artist_changeset/2)
|
|> cast_embed(:artists, with: &artist_changeset/2)
|
||||||
|> generate_image_data_hash()
|
|> generate_cover_hash()
|
||||||
|> validate_required([:type, :title, :musicbrainz_id, :release, :genres])
|
|> validate_required([:type, :title, :musicbrainz_id, :release, :genres])
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -58,28 +58,28 @@ defmodule MusicLibrary.Records.Record do
|
|||||||
|> put_embed(:artists, artists_attrs)
|
|> put_embed(:artists, artists_attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_image_data(record, image_data) do
|
def add_cover_data(record, cover_data) do
|
||||||
record
|
record
|
||||||
|> change(image_data: image_data)
|
|> change(cover_data: cover_data)
|
||||||
|> generate_image_data_hash()
|
|> generate_cover_hash()
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_image_data_hash(record = %__MODULE__{image_data: image_data}) do
|
def generate_cover_hash(record = %__MODULE__{cover_data: cover_data}) do
|
||||||
hash = :crypto.hash(:sha256, image_data) |> Base.encode16()
|
hash = :crypto.hash(:sha256, cover_data) |> Base.encode16()
|
||||||
|
|
||||||
record
|
record
|
||||||
|> change()
|
|> change()
|
||||||
|> put_change(:image_data_hash, hash)
|
|> put_change(:cover_hash, hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_image_data_hash(changeset) do
|
def generate_cover_hash(changeset) do
|
||||||
case get_change(changeset, :image_data) do
|
case get_change(changeset, :cover_data) do
|
||||||
nil ->
|
nil ->
|
||||||
changeset
|
changeset
|
||||||
|
|
||||||
image_data ->
|
cover_data ->
|
||||||
hash = :crypto.hash(:sha256, image_data) |> Base.encode16()
|
hash = :crypto.hash(:sha256, cover_data) |> Base.encode16()
|
||||||
put_change(changeset, :image_data_hash, hash)
|
put_change(changeset, :cover_hash, hash)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ defmodule MusicLibraryWeb.ImageController do
|
|||||||
alias MusicLibrary.Records
|
alias MusicLibrary.Records
|
||||||
|
|
||||||
def show(conn, %{"record_id" => record_id}) do
|
def show(conn, %{"record_id" => record_id}) do
|
||||||
case Records.get_image!(record_id) do
|
case Records.get_cover!(record_id) do
|
||||||
nil ->
|
nil ->
|
||||||
send_resp(conn, 404, "Not found")
|
send_resp(conn, 404, "Not found")
|
||||||
|
|
||||||
%{image_data: image_data, image_data_hash: etag} ->
|
%{cover_data: cover_data, cover_hash: etag} ->
|
||||||
case get_req_header(conn, "if-none-match") do
|
case get_req_header(conn, "if-none-match") do
|
||||||
[^etag] ->
|
[^etag] ->
|
||||||
send_resp(conn, 304, "")
|
send_resp(conn, 304, "")
|
||||||
@@ -17,7 +17,7 @@ defmodule MusicLibraryWeb.ImageController do
|
|||||||
conn
|
conn
|
||||||
|> put_resp_content_type("image/jpeg", "utf-8")
|
|> put_resp_content_type("image/jpeg", "utf-8")
|
||||||
|> put_resp_header("etag", etag)
|
|> put_resp_header("etag", etag)
|
||||||
|> send_resp(200, image_data)
|
|> send_resp(200, cover_data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ defmodule MusicLibraryWeb.RecordLive.FormComponent do
|
|||||||
def mount(socket) do
|
def mount(socket) do
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> allow_upload(:image_data, accept: ~w(.jpg .jpeg), max_entries: 1)}
|
|> allow_upload(:cover_data, accept: ~w(.jpg .jpeg), max_entries: 1)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -35,12 +35,12 @@ defmodule MusicLibraryWeb.RecordLive.FormComponent do
|
|||||||
/>
|
/>
|
||||||
<.input field={@form[:release]} type="text" label="Release" />
|
<.input field={@form[:release]} type="text" label="Release" />
|
||||||
<div>
|
<div>
|
||||||
<.label for={@uploads.image_data.ref}>
|
<.label for={@uploads.cover_data.ref}>
|
||||||
Cover art
|
Cover art
|
||||||
</.label>
|
</.label>
|
||||||
<.live_file_input
|
<.live_file_input
|
||||||
class="mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6"
|
class="mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6"
|
||||||
upload={@uploads.image_data}
|
upload={@uploads.cover_data}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<:actions>
|
<:actions>
|
||||||
@@ -68,19 +68,19 @@ defmodule MusicLibraryWeb.RecordLive.FormComponent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("save", %{"record" => record_params}, socket) do
|
def handle_event("save", %{"record" => record_params}, socket) do
|
||||||
uploaded_images =
|
uploaded_covers =
|
||||||
consume_uploaded_entries(socket, :image_data, fn %{path: path}, _entry ->
|
consume_uploaded_entries(socket, :cover_data, fn %{path: path}, _entry ->
|
||||||
{:ok, File.read!(path)}
|
{:ok, File.read!(path)}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
save_record(socket, record_params, uploaded_images)
|
save_record(socket, record_params, uploaded_covers)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_record(socket, record_params, uploaded_images) do
|
defp save_record(socket, record_params, uploaded_covers) do
|
||||||
params =
|
params =
|
||||||
case uploaded_images do
|
case uploaded_covers do
|
||||||
[] -> record_params
|
[] -> record_params
|
||||||
[image_path] -> Map.put(record_params, "image_data", image_path)
|
[cover_data] -> Map.put(record_params, "cover_data", cover_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
case Records.update_record(socket.assigns.record, params) do
|
case Records.update_record(socket.assigns.record, params) do
|
||||||
|
|||||||
@@ -23,12 +23,12 @@
|
|||||||
rows={@streams.records}
|
rows={@streams.records}
|
||||||
row_click={fn {_id, record} -> JS.navigate(~p"/records/#{record}") end}
|
row_click={fn {_id, record} -> JS.navigate(~p"/records/#{record}") end}
|
||||||
>
|
>
|
||||||
<:col :let={{_id, record}} label="Image">
|
<:col :let={{_id, record}} label="Cover">
|
||||||
<span class="relative inline-block drop-shadow-xl">
|
<span class="relative inline-block drop-shadow-xl">
|
||||||
<img
|
<img
|
||||||
class="max-w-16 rounded-lg"
|
class="max-w-16 rounded-lg"
|
||||||
alt={record.title}
|
alt={record.title}
|
||||||
src={~p"/images/#{record.id}?vsn=#{record.image_data_hash || ""}"}
|
src={~p"/images/#{record.id}?vsn=#{record.cover_hash || ""}"}
|
||||||
/>
|
/>
|
||||||
<span class="absolute right-1 bottom-0 block text-white drop-shadow-md">
|
<span class="absolute right-1 bottom-0 block text-white drop-shadow-md">
|
||||||
<%= Records.Record.format_short_label(record.format) %>
|
<%= Records.Record.format_short_label(record.format) %>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<span class="relative block md:inline-block drop-shadow">
|
<span class="relative block md:inline-block drop-shadow">
|
||||||
<img
|
<img
|
||||||
class="w-full"
|
class="w-full"
|
||||||
src={~p"/images/#{@record.id}?vsn=#{@record.image_data_hash}"}
|
src={~p"/images/#{@record.id}?vsn=#{@record.cover_hash || ""}"}
|
||||||
alt={@record.title}
|
alt={@record.title}
|
||||||
/>
|
/>
|
||||||
<span class="absolute right-2 bottom-1 block text-white drop-shadow-md">
|
<span class="absolute right-2 bottom-1 block text-white drop-shadow-md">
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
defmodule Obsidian.Entry do
|
defmodule Obsidian.Entry do
|
||||||
defstruct [:type, :musicbrainz_id, :title, :release, :image_url, :genres]
|
defstruct [:type, :musicbrainz_id, :title, :release, :cover_url, :genres]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ defmodule Obsidian.Parser do
|
|||||||
musicbrainz_id: meta["id"],
|
musicbrainz_id: meta["id"],
|
||||||
title: meta["title"],
|
title: meta["title"],
|
||||||
release: meta["year"] |> parse_release(),
|
release: meta["year"] |> parse_release(),
|
||||||
image_url: meta["image"],
|
cover_url: meta["image"],
|
||||||
genres: meta["genres"]
|
genres: meta["genres"]
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
defmodule MusicLibrary.Repo.Migrations.RenameImageColumnsToCover do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
rename table(:records), :image_url, to: :cover_url
|
||||||
|
rename table(:records), :image_data, to: :cover_data
|
||||||
|
rename table(:records), :image_data_hash, to: :cover_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
rename table(:records), :cover_url, to: :image_url
|
||||||
|
rename table(:records), :cover_data, to: :image_data
|
||||||
|
rename table(:records), :cover_hash, to: :image_data_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -15,7 +15,7 @@ defmodule Obsidian.ParserTest do
|
|||||||
musicbrainz_id: "20790e26-98e4-3ad3-a67f-b674758b942d",
|
musicbrainz_id: "20790e26-98e4-3ad3-a67f-b674758b942d",
|
||||||
title: "Marbles",
|
title: "Marbles",
|
||||||
release: "2004",
|
release: "2004",
|
||||||
image_url:
|
cover_url:
|
||||||
"https://coverartarchive.org/release-group/20790e26-98e4-3ad3-a67f-b674758b942d/front",
|
"https://coverartarchive.org/release-group/20790e26-98e4-3ad3-a67f-b674758b942d/front",
|
||||||
genres: [
|
genres: [
|
||||||
"alternative rock",
|
"alternative rock",
|
||||||
@@ -36,7 +36,7 @@ defmodule Obsidian.ParserTest do
|
|||||||
{:ok,
|
{:ok,
|
||||||
%Entry{
|
%Entry{
|
||||||
genres: ["classic rock", "pop", "pop rock", "rock"],
|
genres: ["classic rock", "pop", "pop rock", "rock"],
|
||||||
image_url:
|
cover_url:
|
||||||
"https://coverartarchive.org/release-group/950092d6-45f6-4269-87da-99a9ff2fcc52/front",
|
"https://coverartarchive.org/release-group/950092d6-45f6-4269-87da-99a9ff2fcc52/front",
|
||||||
musicbrainz_id: "950092d6-45f6-4269-87da-99a9ff2fcc52",
|
musicbrainz_id: "950092d6-45f6-4269-87da-99a9ff2fcc52",
|
||||||
title: "Guardians of the Galaxy: Awesome Mix, Vol. 1",
|
title: "Guardians of the Galaxy: Awesome Mix, Vol. 1",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ defmodule MusicLibrary.RecordsFixtures do
|
|||||||
"Thick as a Brick"
|
"Thick as a Brick"
|
||||||
]
|
]
|
||||||
# While it would be great to have this random, it's ok to use one single image
|
# While it would be great to have this random, it's ok to use one single image
|
||||||
@image_data_path "#{__DIR__}/marillion-marbles.jpg"
|
@cover_data_path "#{__DIR__}/marillion-marbles.jpg"
|
||||||
|
|
||||||
def record_fixture(attrs \\ %{}) do
|
def record_fixture(attrs \\ %{}) do
|
||||||
musicbrainz_id = Ecto.UUID.generate()
|
musicbrainz_id = Ecto.UUID.generate()
|
||||||
@@ -38,8 +38,8 @@ defmodule MusicLibrary.RecordsFixtures do
|
|||||||
attrs
|
attrs
|
||||||
|> Enum.into(%{
|
|> Enum.into(%{
|
||||||
genres: Enum.take_random(@genres, :rand.uniform(3)),
|
genres: Enum.take_random(@genres, :rand.uniform(3)),
|
||||||
image_url: "https://coverartarchive.org/release-group/#{musicbrainz_id}/front",
|
cover_url: "https://coverartarchive.org/release-group/#{musicbrainz_id}/front",
|
||||||
image_data: File.read!(@image_data_path),
|
cover_data: File.read!(@cover_data_path),
|
||||||
musicbrainz_id: musicbrainz_id,
|
musicbrainz_id: musicbrainz_id,
|
||||||
title: Enum.random(@titles),
|
title: Enum.random(@titles),
|
||||||
type: :album,
|
type: :album,
|
||||||
|
|||||||
Reference in New Issue
Block a user