mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package model
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Kind struct {
|
|
prefix string
|
|
name string
|
|
}
|
|
|
|
func (k Kind) String() string {
|
|
return k.name
|
|
}
|
|
|
|
// Prefix is the short token used in artwork ids and the item_artwork.item_kind column.
|
|
func (k Kind) Prefix() string {
|
|
return k.prefix
|
|
}
|
|
|
|
var (
|
|
KindMediaFileArtwork = Kind{"mf", "media_file"}
|
|
KindArtistArtwork = Kind{"ar", "artist"}
|
|
KindAlbumArtwork = Kind{"al", "album"}
|
|
KindPlaylistArtwork = Kind{"pl", "playlist"}
|
|
KindDiscArtwork = Kind{"dc", "disc"}
|
|
KindRadioArtwork = Kind{"ra", "radio"}
|
|
)
|
|
|
|
var artworkKindMap = map[string]Kind{
|
|
KindMediaFileArtwork.prefix: KindMediaFileArtwork,
|
|
KindArtistArtwork.prefix: KindArtistArtwork,
|
|
KindAlbumArtwork.prefix: KindAlbumArtwork,
|
|
KindPlaylistArtwork.prefix: KindPlaylistArtwork,
|
|
KindDiscArtwork.prefix: KindDiscArtwork,
|
|
KindRadioArtwork.prefix: KindRadioArtwork,
|
|
}
|
|
|
|
type ArtworkID struct {
|
|
Kind Kind
|
|
ID string
|
|
Hash string // content-hash suffix; "" = unknown/none
|
|
LastUpdate time.Time // legacy: populated only when parsing old _<hexTimestamp> tokens
|
|
}
|
|
|
|
func (id ArtworkID) String() string {
|
|
if id.ID == "" {
|
|
return ""
|
|
}
|
|
s := fmt.Sprintf("%s-%s", id.Kind.prefix, id.ID)
|
|
if id.Hash != "" {
|
|
return s + "_" + id.Hash
|
|
}
|
|
return s
|
|
}
|
|
|
|
func NewArtworkID(kind Kind, id string, lastUpdate *time.Time) ArtworkID {
|
|
artID := ArtworkID{Kind: kind, ID: id}
|
|
if lastUpdate != nil {
|
|
artID.LastUpdate = *lastUpdate
|
|
}
|
|
return artID
|
|
}
|
|
|
|
func ParseArtworkID(id string) (ArtworkID, error) {
|
|
parts := strings.SplitN(id, "-", 2)
|
|
if len(parts) != 2 {
|
|
return ArtworkID{}, errors.New("invalid artwork id")
|
|
}
|
|
kind, ok := artworkKindMap[parts[0]]
|
|
if !ok {
|
|
return ArtworkID{}, errors.New("invalid artwork kind")
|
|
}
|
|
parsedID := ArtworkID{
|
|
Kind: kind,
|
|
ID: parts[1],
|
|
}
|
|
parts = strings.SplitN(parts[1], "_", 2)
|
|
if len(parts) == 2 {
|
|
parsedID.ID = parts[0]
|
|
suffix := parts[1]
|
|
switch {
|
|
// Hash detection must come first: a 16-hex value with the high bit set overflows int64.
|
|
case isImageHash(suffix):
|
|
parsedID.Hash = suffix
|
|
case suffix != "0":
|
|
if lastUpdate, err := strconv.ParseInt(suffix, 16, 64); err == nil {
|
|
parsedID.LastUpdate = time.Unix(lastUpdate, 0)
|
|
}
|
|
}
|
|
}
|
|
return parsedID, nil
|
|
}
|
|
|
|
// isImageHash reports whether s is a 16-char lowercase-hex XXH3-64 content hash.
|
|
func isImageHash(s string) bool {
|
|
if len(s) != 16 {
|
|
return false
|
|
}
|
|
for _, c := range s {
|
|
if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func MustParseArtworkID(id string) ArtworkID {
|
|
artID, err := ParseArtworkID(id)
|
|
if err != nil {
|
|
panic(artID)
|
|
}
|
|
return artID
|
|
}
|
|
|
|
func DiscArtworkID(albumID string, discNumber int) string {
|
|
return fmt.Sprintf("%s:%d", albumID, discNumber)
|
|
}
|
|
|
|
func ParseDiscArtworkID(id string) (albumID string, discNumber int, err error) {
|
|
parts := strings.SplitN(id, ":", 2)
|
|
if len(parts) != 2 || parts[1] == "" {
|
|
return "", 0, errors.New("invalid disc artwork id")
|
|
}
|
|
num, err := strconv.Atoi(parts[1])
|
|
if err != nil {
|
|
return "", 0, fmt.Errorf("invalid disc number in artwork id: %w", err)
|
|
}
|
|
return parts[0], num, nil
|
|
}
|
|
|
|
func artworkIDFromAlbum(al Album) ArtworkID {
|
|
return ArtworkID{Kind: KindAlbumArtwork, ID: al.ID, Hash: al.ImageHash}
|
|
}
|
|
|
|
func artworkIDFromMediaFile(mf MediaFile) ArtworkID {
|
|
return ArtworkID{Kind: KindMediaFileArtwork, ID: mf.ID, Hash: mf.ImageHash}
|
|
}
|
|
|
|
func artworkIDFromPlaylist(pls Playlist) ArtworkID {
|
|
return ArtworkID{Kind: KindPlaylistArtwork, ID: pls.ID, Hash: pls.ImageHash}
|
|
}
|
|
|
|
func artworkIDFromArtist(ar Artist) ArtworkID {
|
|
return ArtworkID{Kind: KindArtistArtwork, ID: ar.ID, Hash: ar.ImageHash}
|
|
}
|
|
|
|
func artworkIDFromRadio(r Radio) ArtworkID {
|
|
return ArtworkID{Kind: KindRadioArtwork, ID: r.ID, Hash: r.ImageHash}
|
|
}
|