diff --git a/lib/music_library/records/tracklist_pdf.ex b/lib/music_library/records/tracklist_pdf.ex index 2388b34d..031cd5bd 100644 --- a/lib/music_library/records/tracklist_pdf.ex +++ b/lib/music_library/records/tracklist_pdf.ex @@ -1,6 +1,6 @@ defmodule MusicLibrary.Records.TracklistPdf do @moduledoc """ - Generates 120mm x 120mm PDF tracklists from record and release data via Typst. + Generates 120mm x 120mm PDF tracklists from release data via Typst. """ alias MusicBrainz.Release @@ -21,23 +21,21 @@ defmodule MusicLibrary.Records.TracklistPdf do # including the header area consumed by artist name and album title. @capacities %{8 => 35, 7 => 38, 6 => 45, 5 => 53} - @spec generate(MusicLibrary.Records.Record.t(), Release.t()) :: - {:ok, binary()} | {:error, term()} - def generate(record, release) do - markup = build_markup(record, release.media) + @spec generate(Release.t()) :: {:ok, binary()} | {:error, term()} + def generate(release) do + markup = build_markup(release, release.media) Typst.render_to_pdf(markup) end - @spec generate_medium(MusicLibrary.Records.Record.t(), Release.t(), integer()) :: - {:ok, binary()} | {:error, term()} - def generate_medium(record, release, medium_number) do + @spec generate_medium(Release.t(), integer()) :: {:ok, binary()} | {:error, term()} + def generate_medium(release, medium_number) do case Release.get_medium(release, medium_number) do nil -> {:error, :medium_not_found} - medium -> generate(record, %{release | media: [medium]}) + medium -> generate(%{release | media: [medium]}) end end - defp build_markup(record, media) do + defp build_markup(release, media) do media_count = length(media) track_count = Enum.sum(Enum.map(media, &length(&1.tracks))) header_count = if media_count > 1, do: media_count, else: 0 @@ -54,9 +52,9 @@ defmodule MusicLibrary.Records.TracklistPdf do #place(top + center, scope: "parent", float: true)[ #align(center)[ - #text(size: 10pt, weight: "bold")[#{Format.escape(artist_names(record))}] + #text(size: 10pt, weight: "bold")[#{Format.escape(artist_names(release))}] #linebreak() - #text(size: 9pt, style: "italic")[#{Format.escape(record.title)}] + #text(size: 9pt, style: "italic")[#{Format.escape(release.title)}] ] #v(3mm) ] @@ -138,8 +136,8 @@ defmodule MusicLibrary.Records.TracklistPdf do Map.fetch!(@capacities, font_size) * columns end - defp artist_names(record) do - Enum.map_join(record.artists, fn artist -> + defp artist_names(release) do + Enum.map_join(release.artists, fn artist -> artist.name <> (artist.joinphrase || "") end) end diff --git a/lib/music_library_web/components/release.ex b/lib/music_library_web/components/release.ex index fa5f8c19..72fd88dd 100644 --- a/lib/music_library_web/components/release.ex +++ b/lib/music_library_web/components/release.ex @@ -620,7 +620,7 @@ defmodule MusicLibraryWeb.Components.Release do release = socket.assigns.release_with_tracks.result record = socket.assigns.record - case TracklistPdf.generate(record, release) do + case TracklistPdf.generate(release) do {:ok, pdf_binary} -> filename = "#{record.title} - Tracklist.pdf" @@ -654,7 +654,7 @@ defmodule MusicLibraryWeb.Components.Release do record = socket.assigns.record {number, ""} = Integer.parse(number) - case TracklistPdf.generate_medium(record, release, number) do + case TracklistPdf.generate_medium(release, number) do {:ok, pdf_binary} -> filename = "#{record.title} - Disc #{number} - Tracklist.pdf" diff --git a/test/music_library/records/tracklist_pdf_test.exs b/test/music_library/records/tracklist_pdf_test.exs index dbbc5547..383313e2 100644 --- a/test/music_library/records/tracklist_pdf_test.exs +++ b/test/music_library/records/tracklist_pdf_test.exs @@ -2,110 +2,106 @@ defmodule MusicLibrary.Records.TracklistPdfTest do use ExUnit.Case, async: true alias MusicBrainz.Release - alias MusicLibrary.Records.{Record, TracklistPdf} + alias MusicLibrary.Records.TracklistPdf @pdf_magic_bytes <<37, 80, 68, 70>> - describe "generate/2" do + describe "generate/1" do test "generates PDF for single-disc release" do - record = build_record(%{title: "OK Computer", artists: [%{name: "Radiohead"}]}) - release = - build_release([ - build_medium(1, [ - build_track(1, "Airbag", 284_533), - build_track(2, "Paranoid Android", 383_000) - ]) - ]) + build_release( + [ + build_medium(1, [ + build_track(1, "Airbag", 284_533), + build_track(2, "Paranoid Android", 383_000) + ]) + ], + title: "OK Computer", + artists: [%{name: "Radiohead"}] + ) - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end test "generates PDF for multi-disc release" do - record = build_record(%{title: "Marbles", artists: [%{name: "Marillion"}]}) api_response = MusicBrainz.Fixtures.Release.release_with_media(:marbles) release = Release.from_api_response(api_response) - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end test "renders artist joinphrase correctly" do - record = - build_record(%{ + release = + build_release( + [ + build_medium(1, [build_track(1, "Track One", 200_000)]) + ], title: "Collab Album", artists: [ %{name: "Artist A", joinphrase: " & "}, %{name: "Artist B"} ] - }) + ) - release = - build_release([ - build_medium(1, [build_track(1, "Track One", 200_000)]) - ]) - - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end test "handles track without duration" do - record = build_record(%{title: "Test Album", artists: [%{name: "Test Artist"}]}) - release = - build_release([ - build_medium(1, [ - build_track(1, "Has Duration", 180_000), - build_track(2, "No Duration", nil) - ]) - ]) + build_release( + [ + build_medium(1, [ + build_track(1, "Has Duration", 180_000), + build_track(2, "No Duration", nil) + ]) + ], + title: "Test Album", + artists: [%{name: "Test Artist"}] + ) - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end test "handles special characters in title" do - record = - build_record(%{ + release = + build_release( + [ + build_medium(1, [ + build_track(1, "Track *starring* @someone", 200_000), + build_track(2, "#Hashtag Title", 180_000) + ]) + ], title: "Album *with* #special @chars", artists: [%{name: "Artist #1"}] - }) + ) - release = - build_release([ - build_medium(1, [ - build_track(1, "Track *starring* @someone", 200_000), - build_track(2, "#Hashtag Title", 180_000) - ]) - ]) - - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end end - describe "generate_medium/3" do + describe "generate_medium/2" do test "generates PDF for a single medium from a multi-disc release" do - record = build_record(%{title: "Marbles", artists: [%{name: "Marillion"}]}) api_response = MusicBrainz.Fixtures.Release.release_with_media(:marbles) release = Release.from_api_response(api_response) assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = - TracklistPdf.generate_medium(record, release, 1) + TracklistPdf.generate_medium(release, 1) assert pdf_page_count(pdf) == 1 end test "returns error for non-existent medium number" do - record = build_record(%{title: "Test Album", artists: [%{name: "Test Artist"}]}) - release = build_release([ build_medium(1, [build_track(1, "Track One", 200_000)]) ]) - assert {:error, :medium_not_found} = TracklistPdf.generate_medium(record, release, 99) + assert {:error, :medium_not_found} = TracklistPdf.generate_medium(release, 99) end end @@ -132,62 +128,55 @@ defmodule MusicLibrary.Records.TracklistPdfTest do end end - describe "generate/2 with many tracks" do + describe "generate/1 with many tracks" do test "generates single-page PDF for large single-disc release" do - record = build_record(%{title: "Long Album", artists: [%{name: "Prolific Artist"}]}) tracks = Enum.map(1..40, &build_track(&1, "Track #{&1}", 180_000)) - release = build_release([build_medium(1, tracks)]) + release = + build_release([build_medium(1, tracks)], + title: "Long Album", + artists: [%{name: "Prolific Artist"}] + ) - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end test "generates single-page PDF for large multi-disc release" do - record = build_record(%{title: "Box Set", artists: [%{name: "Band"}]}) - media = Enum.map(1..4, fn disc -> tracks = Enum.map(1..20, &build_track(&1, "Disc #{disc} Track #{&1}", 200_000)) build_medium(disc, tracks) end) - release = build_release(media) + release = + build_release(media, + title: "Box Set", + artists: [%{name: "Band"}] + ) - assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(record, release) + assert {:ok, <<@pdf_magic_bytes, _::binary>> = pdf} = TracklistPdf.generate(release) assert pdf_page_count(pdf) == 1 end end - defp build_record(attrs) do + defp build_release(media, attrs \\ []) do artists = - Enum.map(Map.get(attrs, :artists, []), fn a -> - %{ - name: a[:name] || a.name, - sort_name: a[:sort_name] || a[:name] || a.name, - musicbrainz_id: "00000000-0000-0000-0000-000000000000", - disambiguation: "", - joinphrase: a[:joinphrase] || "" + Enum.map(Keyword.get(attrs, :artists, []), fn a -> + %Release.Artist{ + id: "00000000-0000-0000-0000-000000000000", + name: Map.get(a, :name), + sort_name: Map.get(a, :sort_name, Map.get(a, :name, "")), + joinphrase: Map.get(a, :joinphrase) } end) - %Record{ - id: Ecto.UUID.generate(), - title: attrs[:title] || "Test Album", - artists: artists, - genres: [], - type: :album, - format: :cd - } - end - - defp build_release(media) do %Release{ id: "00000000-0000-0000-0000-000000000000", - title: "Test Release", + title: Keyword.get(attrs, :title, "Test Release"), disambiguation: nil, packaging: nil, - artists: [], + artists: artists, date: "2024-01-01", barcode: nil, catalog_number: "",