navidrome/model/album.go
Deluan 223e88d481 chore: remove some BFR-related TODOs that are not valid anymore
Signed-off-by: Deluan <deluan@navidrome.org>
2025-03-23 11:37:20 -04:00

142 lines
6.3 KiB
Go

package model
import (
"iter"
"math"
"sync"
"time"
"github.com/gohugoio/hashstructure"
)
type Album struct {
Annotations `structs:"-" hash:"ignore"`
ID string `structs:"id" json:"id"`
LibraryID int `structs:"library_id" json:"libraryId"`
Name string `structs:"name" json:"name"`
EmbedArtPath string `structs:"embed_art_path" json:"-"`
AlbumArtistID string `structs:"album_artist_id" json:"albumArtistId"` // Deprecated, use Participants
// AlbumArtist is the display name used for the album artist.
AlbumArtist string `structs:"album_artist" json:"albumArtist"`
MaxYear int `structs:"max_year" json:"maxYear"`
MinYear int `structs:"min_year" json:"minYear"`
Date string `structs:"date" json:"date,omitempty"`
MaxOriginalYear int `structs:"max_original_year" json:"maxOriginalYear"`
MinOriginalYear int `structs:"min_original_year" json:"minOriginalYear"`
OriginalDate string `structs:"original_date" json:"originalDate,omitempty"`
ReleaseDate string `structs:"release_date" json:"releaseDate,omitempty"`
Compilation bool `structs:"compilation" json:"compilation"`
Comment string `structs:"comment" json:"comment,omitempty"`
SongCount int `structs:"song_count" json:"songCount"`
Duration float32 `structs:"duration" json:"duration"`
Size int64 `structs:"size" json:"size"`
Discs Discs `structs:"discs" json:"discs,omitempty"`
SortAlbumName string `structs:"sort_album_name" json:"sortAlbumName,omitempty"`
SortAlbumArtistName string `structs:"sort_album_artist_name" json:"sortAlbumArtistName,omitempty"`
OrderAlbumName string `structs:"order_album_name" json:"orderAlbumName"`
OrderAlbumArtistName string `structs:"order_album_artist_name" json:"orderAlbumArtistName"`
CatalogNum string `structs:"catalog_num" json:"catalogNum,omitempty"`
MbzAlbumID string `structs:"mbz_album_id" json:"mbzAlbumId,omitempty"`
MbzAlbumArtistID string `structs:"mbz_album_artist_id" json:"mbzAlbumArtistId,omitempty"`
MbzAlbumType string `structs:"mbz_album_type" json:"mbzAlbumType,omitempty"`
MbzAlbumComment string `structs:"mbz_album_comment" json:"mbzAlbumComment,omitempty"`
MbzReleaseGroupID string `structs:"mbz_release_group_id" json:"mbzReleaseGroupId,omitempty"`
FolderIDs []string `structs:"folder_ids" json:"-" hash:"set"` // All folders that contain media_files for this album
ExplicitStatus string `structs:"explicit_status" json:"explicitStatus"`
// External metadata fields
Description string `structs:"description" json:"description,omitempty" hash:"ignore"`
SmallImageUrl string `structs:"small_image_url" json:"smallImageUrl,omitempty" hash:"ignore"`
MediumImageUrl string `structs:"medium_image_url" json:"mediumImageUrl,omitempty" hash:"ignore"`
LargeImageUrl string `structs:"large_image_url" json:"largeImageUrl,omitempty" hash:"ignore"`
ExternalUrl string `structs:"external_url" json:"externalUrl,omitempty" hash:"ignore"`
ExternalInfoUpdatedAt *time.Time `structs:"external_info_updated_at" json:"externalInfoUpdatedAt" hash:"ignore"`
Genre string `structs:"genre" json:"genre" hash:"ignore"` // Easy access to the most common genre
Genres Genres `structs:"-" json:"genres" hash:"ignore"` // Easy access to all genres for this album
Tags Tags `structs:"tags" json:"tags,omitempty" hash:"ignore"` // All imported tags for this album
Participants Participants `structs:"participants" json:"participants" hash:"ignore"` // All artists that participated in this album
Missing bool `structs:"missing" json:"missing"` // If all file of the album ar missing
ImportedAt time.Time `structs:"imported_at" json:"importedAt" hash:"ignore"` // When this album was imported/updated
CreatedAt time.Time `structs:"created_at" json:"createdAt"` // Oldest CreatedAt for all songs in this album
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"` // Newest UpdatedAt for all songs in this album
}
func (a Album) CoverArtID() ArtworkID {
return artworkIDFromAlbum(a)
}
// Equals compares two Album structs, ignoring calculated fields
func (a Album) Equals(other Album) bool {
// Normalize float32 values to avoid false negatives
a.Duration = float32(math.Floor(float64(a.Duration)))
other.Duration = float32(math.Floor(float64(other.Duration)))
opts := &hashstructure.HashOptions{
IgnoreZeroValue: true,
ZeroNil: true,
}
hash1, _ := hashstructure.Hash(a, opts)
hash2, _ := hashstructure.Hash(other, opts)
return hash1 == hash2
}
// AlbumLevelTags contains all Tags marked as `album: true` in the mappings.yml file. They are not
// "first-class citizens" in the Album struct, but are still stored in the album table, in the `tags` column.
var AlbumLevelTags = sync.OnceValue(func() map[TagName]struct{} {
tags := make(map[TagName]struct{})
m := TagMappings()
for t, conf := range m {
if conf.Album {
tags[t] = struct{}{}
}
}
return tags
})
func (a *Album) SetTags(tags TagList) {
a.Tags = tags.GroupByFrequency()
for k := range a.Tags {
if _, ok := AlbumLevelTags()[k]; !ok {
delete(a.Tags, k)
}
}
}
type Discs map[int]string
func (d Discs) Add(discNumber int, discSubtitle string) {
d[discNumber] = discSubtitle
}
type DiscID struct {
AlbumID string `json:"albumId"`
ReleaseDate string `json:"releaseDate"`
DiscNumber int `json:"discNumber"`
}
type Albums []Album
type AlbumCursor iter.Seq2[Album, error]
type AlbumRepository interface {
CountAll(...QueryOptions) (int64, error)
Exists(id string) (bool, error)
Put(*Album) error
UpdateExternalInfo(*Album) error
Get(id string) (*Album, error)
GetAll(...QueryOptions) (Albums, error)
// The following methods are used exclusively by the scanner:
Touch(ids ...string) error
TouchByMissingFolder() (int64, error)
GetTouchedAlbums(libID int) (AlbumCursor, error)
RefreshPlayCounts() (int64, error)
CopyAttributes(fromID, toID string, columns ...string) error
AnnotatedRepository
SearchableRepository[Albums]
}