feat(S02/T01): Added CoverArtGroupURL for release-group cover art, conc…
- backend/explore/coverart.go - backend/explore/explore.go - backend/explore/coverart_test.go - frontend/wailsjs/go/explore/Service.js - frontend/wailsjs/go/explore/Service.d.ts
This commit is contained in:
@@ -2,7 +2,10 @@ package explore
|
||||
|
||||
import "fmt"
|
||||
|
||||
const coverArtBaseURL = "https://coverartarchive.org/release"
|
||||
const (
|
||||
coverArtBaseURL = "https://coverartarchive.org/release"
|
||||
coverArtGroupBaseURL = "https://coverartarchive.org/release-group"
|
||||
)
|
||||
|
||||
// CoverArtURL returns the Cover Art Archive URL for the 250px
|
||||
// front cover of the given release MBID.
|
||||
@@ -16,3 +19,18 @@ func CoverArtURL(releaseMBID string) string {
|
||||
func CoverArtURLSize(releaseMBID string, size int) string {
|
||||
return fmt.Sprintf("%s/%s/front-%d", coverArtBaseURL, releaseMBID, size)
|
||||
}
|
||||
|
||||
// CoverArtGroupURL returns the Cover Art Archive URL for the 250px
|
||||
// front cover of the given release group MBID. Search results
|
||||
// return release group MBIDs (not release MBIDs), so this is the
|
||||
// correct endpoint for displaying cover art in search results.
|
||||
func CoverArtGroupURL(releaseGroupMBID string) string {
|
||||
return fmt.Sprintf("%s/%s/front-250", coverArtGroupBaseURL, releaseGroupMBID)
|
||||
}
|
||||
|
||||
// CoverArtGroupURLSize returns the Cover Art Archive URL for the
|
||||
// front cover of the given release group MBID at the specified
|
||||
// pixel size. Common sizes are 250, 500, and 1200.
|
||||
func CoverArtGroupURLSize(releaseGroupMBID string, size int) string {
|
||||
return fmt.Sprintf("%s/%s/front-%d", coverArtGroupBaseURL, releaseGroupMBID, size)
|
||||
}
|
||||
|
||||
@@ -50,3 +50,48 @@ func TestCoverArtURLSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoverArtGroupURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mbid := "abc-123"
|
||||
|
||||
got := explore.CoverArtGroupURL(mbid)
|
||||
want := "https://coverartarchive.org/release-group/abc-123/front-250"
|
||||
|
||||
if got != want {
|
||||
t.Errorf("CoverArtGroupURL(%q) = %q, want %q", mbid, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoverArtGroupURLSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mbid := "abc-123"
|
||||
|
||||
tests := []struct {
|
||||
size int
|
||||
want string
|
||||
}{
|
||||
{
|
||||
250,
|
||||
"https://coverartarchive.org/release-group/abc-123/front-250",
|
||||
},
|
||||
{
|
||||
500,
|
||||
"https://coverartarchive.org/release-group/abc-123/front-500",
|
||||
},
|
||||
{
|
||||
1200,
|
||||
"https://coverartarchive.org/release-group/abc-123/front-1200",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := explore.CoverArtGroupURLSize(mbid, tt.size)
|
||||
if got != tt.want {
|
||||
t.Errorf("CoverArtGroupURLSize(%q, %d) = %q, want %q",
|
||||
mbid, tt.size, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package explore
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"yellowjacket/backend/database"
|
||||
)
|
||||
@@ -116,3 +117,111 @@ func (e *Service) SimilarArtists(artistMBID string) ([]LBSimilarArtist, error) {
|
||||
func (e *Service) CoverArtURL(releaseMBID string) string {
|
||||
return CoverArtURL(releaseMBID)
|
||||
}
|
||||
|
||||
// CoverArtGroupURL returns the Cover Art Archive URL for a release
|
||||
// group's front cover at the default 250px size. This is the
|
||||
// correct endpoint for search results, which return release group
|
||||
// MBIDs rather than individual release MBIDs.
|
||||
func (e *Service) CoverArtGroupURL(releaseGroupMBID string) string {
|
||||
return CoverArtGroupURL(releaseGroupMBID)
|
||||
}
|
||||
|
||||
// Search concurrently queries MusicBrainz for artists, release
|
||||
// groups, and recordings matching the query, returning aggregated
|
||||
// results in a single round-trip. If any sub-search fails the
|
||||
// error is logged and the remaining results are still returned.
|
||||
func (e *Service) Search(query string) (*MBSearchResult, error) {
|
||||
e.logger.Info("search started", "query", query)
|
||||
|
||||
var (
|
||||
result MBSearchResult
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
type searchFunc struct {
|
||||
name string
|
||||
fn func()
|
||||
}
|
||||
|
||||
searches := []searchFunc{
|
||||
{
|
||||
name: "artists",
|
||||
fn: func() {
|
||||
artists, err := e.mb.SearchArtists(e.ctx, query, 0)
|
||||
if err != nil {
|
||||
e.logger.Warn("search sub-call failed",
|
||||
"entity", "artists",
|
||||
"query", query,
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
result.Artists = artists
|
||||
mu.Unlock()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "releaseGroups",
|
||||
fn: func() {
|
||||
rgs, err := e.mb.SearchReleaseGroups(e.ctx, query, 0)
|
||||
if err != nil {
|
||||
e.logger.Warn("search sub-call failed",
|
||||
"entity", "releaseGroups",
|
||||
"query", query,
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
result.ReleaseGroups = rgs
|
||||
mu.Unlock()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recordings",
|
||||
fn: func() {
|
||||
recs, err := e.mb.SearchRecordings(e.ctx, query, 0)
|
||||
if err != nil {
|
||||
e.logger.Warn("search sub-call failed",
|
||||
"entity", "recordings",
|
||||
"query", query,
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
result.Recordings = recs
|
||||
mu.Unlock()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
wg.Add(len(searches))
|
||||
|
||||
for _, s := range searches {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
s.fn()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
e.logger.Info("search completed",
|
||||
"query", query,
|
||||
"artists", len(result.Artists),
|
||||
"releaseGroups", len(result.ReleaseGroups),
|
||||
"recordings", len(result.Recordings),
|
||||
)
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
// -- Explore types (inline, matching Go JSON tags) ------------------
|
||||
|
||||
export interface MBArtist {
|
||||
mbid: string;
|
||||
name: string;
|
||||
sortName: string;
|
||||
type: string;
|
||||
country: string;
|
||||
disambiguation: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface MBReleaseGroup {
|
||||
mbid: string;
|
||||
title: string;
|
||||
primaryType: string;
|
||||
secondaryTypes?: string[];
|
||||
firstReleaseDate: string;
|
||||
artistCredit: string;
|
||||
}
|
||||
|
||||
export interface MBRecording {
|
||||
mbid: string;
|
||||
title: string;
|
||||
length: number;
|
||||
artistCredit: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface MBRelease {
|
||||
mbid: string;
|
||||
title: string;
|
||||
date: string;
|
||||
country: string;
|
||||
status: string;
|
||||
tracks?: MBTrack[];
|
||||
}
|
||||
|
||||
export interface MBTrack {
|
||||
position: number;
|
||||
title: string;
|
||||
length: number;
|
||||
mbid: string;
|
||||
}
|
||||
|
||||
export interface MBSearchResult {
|
||||
artists?: MBArtist[];
|
||||
releaseGroups?: MBReleaseGroup[];
|
||||
recordings?: MBRecording[];
|
||||
}
|
||||
|
||||
export interface LBTopRecording {
|
||||
recordingMbid: string;
|
||||
artistName: string;
|
||||
trackName: string;
|
||||
totalListenCount: number;
|
||||
}
|
||||
|
||||
export interface LBSimilarArtist {
|
||||
artistMbid: string;
|
||||
name: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
// -- Service methods ------------------------------------------------
|
||||
|
||||
export function Search(arg1:string):Promise<MBSearchResult>;
|
||||
|
||||
export function SearchArtists(arg1:string):Promise<Array<MBArtist>>;
|
||||
|
||||
export function SearchReleaseGroups(arg1:string):Promise<Array<MBReleaseGroup>>;
|
||||
|
||||
export function SearchRecordings(arg1:string):Promise<Array<MBRecording>>;
|
||||
|
||||
export function LookupArtist(arg1:string):Promise<MBArtist>;
|
||||
|
||||
export function LookupReleaseGroup(arg1:string):Promise<MBReleaseGroup>;
|
||||
|
||||
export function BrowseReleaseGroups(arg1:string):Promise<Array<MBReleaseGroup>>;
|
||||
|
||||
export function BrowseReleases(arg1:string):Promise<Array<MBRelease>>;
|
||||
|
||||
export function TopRecordingsForArtist(arg1:string):Promise<Array<LBTopRecording>>;
|
||||
|
||||
export function SimilarArtists(arg1:string):Promise<Array<LBSimilarArtist>>;
|
||||
|
||||
export function CoverArtURL(arg1:string):Promise<string>;
|
||||
|
||||
export function CoverArtGroupURL(arg1:string):Promise<string>;
|
||||
@@ -0,0 +1,51 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function Search(arg1) {
|
||||
return window['go']['explore']['Service']['Search'](arg1);
|
||||
}
|
||||
|
||||
export function SearchArtists(arg1) {
|
||||
return window['go']['explore']['Service']['SearchArtists'](arg1);
|
||||
}
|
||||
|
||||
export function SearchReleaseGroups(arg1) {
|
||||
return window['go']['explore']['Service']['SearchReleaseGroups'](arg1);
|
||||
}
|
||||
|
||||
export function SearchRecordings(arg1) {
|
||||
return window['go']['explore']['Service']['SearchRecordings'](arg1);
|
||||
}
|
||||
|
||||
export function LookupArtist(arg1) {
|
||||
return window['go']['explore']['Service']['LookupArtist'](arg1);
|
||||
}
|
||||
|
||||
export function LookupReleaseGroup(arg1) {
|
||||
return window['go']['explore']['Service']['LookupReleaseGroup'](arg1);
|
||||
}
|
||||
|
||||
export function BrowseReleaseGroups(arg1) {
|
||||
return window['go']['explore']['Service']['BrowseReleaseGroups'](arg1);
|
||||
}
|
||||
|
||||
export function BrowseReleases(arg1) {
|
||||
return window['go']['explore']['Service']['BrowseReleases'](arg1);
|
||||
}
|
||||
|
||||
export function TopRecordingsForArtist(arg1) {
|
||||
return window['go']['explore']['Service']['TopRecordingsForArtist'](arg1);
|
||||
}
|
||||
|
||||
export function SimilarArtists(arg1) {
|
||||
return window['go']['explore']['Service']['SimilarArtists'](arg1);
|
||||
}
|
||||
|
||||
export function CoverArtURL(arg1) {
|
||||
return window['go']['explore']['Service']['CoverArtURL'](arg1);
|
||||
}
|
||||
|
||||
export function CoverArtGroupURL(arg1) {
|
||||
return window['go']['explore']['Service']['CoverArtGroupURL'](arg1);
|
||||
}
|
||||
Reference in New Issue
Block a user