package library import ( "context" "crypto/sha256" "encoding/hex" "errors" "os" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) type artistImageStore interface { ArtistWithoutImageForFile(context.Context, uuid.UUID) (uuid.UUID, error) SetArtistImageIfMissing(context.Context, uuid.UUID, string) error } type imageRenderer interface { StoreImage(context.Context, []byte) (string, error) } // ArtistImages applies approved portraits to the album artist, never guests. type ArtistImages struct { store artistImageStore images imageRenderer } func NewArtistImages(store *Store, covers *Covers) *ArtistImages { return &ArtistImages{store: store, images: covers} } func (a *ArtistImages) ApplyArtistImage(ctx context.Context, file uuid.UUID, data []byte) error { id, err := a.store.ArtistWithoutImageForFile(ctx, file) if errors.Is(err, ErrNotFound) { return nil } if err != nil { return err } key, err := a.images.StoreImage(ctx, data) if err != nil { return err } return a.store.SetArtistImageIfMissing(ctx, id, key) } func (c *Covers) StoreImage(ctx context.Context, data []byte) (string, error) { if err := os.MkdirAll(c.Dir, 0o755); err != nil { return "", err } sum := sha256.Sum256(data) key := hex.EncodeToString(sum[:8]) _, err := c.render(ctx, data, key) return key, err } func (st *Store) ArtistWithoutImageForFile(ctx context.Context, file uuid.UUID) (uuid.UUID, error) { var id uuid.UUID err := st.pool.QueryRow(ctx, `SELECT a.id FROM media_files f JOIN tracks t ON t.id=f.track_id JOIN albums al ON al.id=t.album_id JOIN artists a ON a.id=al.album_artist_id WHERE f.id=$1 AND a.image_path IS NULL AND a.genius_image_url IS NULL`, file).Scan(&id) if errors.Is(err, pgx.ErrNoRows) { return id, ErrNotFound } return id, err } func (st *Store) SetArtistImageIfMissing(ctx context.Context, id uuid.UUID, key string) error { // Another import or Genius may have supplied a portrait during rendering. _, err := st.pool.Exec(ctx, `UPDATE artists SET image_path=$2 WHERE id=$1 AND image_path IS NULL AND genius_image_url IS NULL`, id, key) return err }