a1f6701bac
- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
213 lines
4.0 KiB
Go
213 lines
4.0 KiB
Go
package musicbrainz
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/metadata-agregator/internal/domain"
|
|
)
|
|
|
|
func mapArtist(mb *mbArtist) *domain.Artist {
|
|
if mb == nil {
|
|
return nil
|
|
}
|
|
|
|
artist := &domain.Artist{
|
|
ID: mb.ID,
|
|
Name: mb.Name,
|
|
SortName: mb.SortName,
|
|
Type: mb.Type,
|
|
Country: mb.Country,
|
|
Description: mb.Disambiguation,
|
|
ExternalIDs: []domain.ExternalID{{
|
|
Source: "musicbrainz",
|
|
SourceID: mb.ID,
|
|
URL: fmt.Sprintf("https://musicbrainz.org/artist/%s", mb.ID),
|
|
}},
|
|
}
|
|
|
|
if mb.LifeSpan.Begin != "" {
|
|
if t := parseDate(mb.LifeSpan.Begin); t != nil {
|
|
artist.FormedDate = t
|
|
}
|
|
}
|
|
|
|
if mb.LifeSpan.End != "" {
|
|
if t := parseDate(mb.LifeSpan.End); t != nil {
|
|
artist.DisbandedDate = t
|
|
}
|
|
}
|
|
|
|
for _, g := range mb.Genres {
|
|
artist.Genres = append(artist.Genres, domain.Genre{
|
|
ID: g.ID,
|
|
Name: g.Name,
|
|
})
|
|
}
|
|
|
|
for _, rel := range mb.Relations {
|
|
if rel.Type == "image" && rel.URL != nil {
|
|
artist.ImageURL = rel.URL.Resource
|
|
break
|
|
}
|
|
}
|
|
|
|
return artist
|
|
}
|
|
|
|
func mapAlbum(mb *mbReleaseGroup, release *mbRelease) *domain.Album {
|
|
if mb == nil {
|
|
return nil
|
|
}
|
|
|
|
album := &domain.Album{
|
|
ID: mb.ID,
|
|
Title: mb.Title,
|
|
Type: mb.PrimaryType,
|
|
ExternalIDs: []domain.ExternalID{{
|
|
Source: "musicbrainz",
|
|
SourceID: mb.ID,
|
|
URL: fmt.Sprintf("https://musicbrainz.org/release-group/%s", mb.ID),
|
|
}},
|
|
}
|
|
|
|
if mb.FirstReleaseDate != "" {
|
|
album.ReleaseDate = parseDate(mb.FirstReleaseDate)
|
|
}
|
|
|
|
for _, ac := range mb.ArtistCredit {
|
|
album.Artists = append(album.Artists, mapArtistCredit(&ac, "primary"))
|
|
}
|
|
|
|
for _, g := range mb.Genres {
|
|
album.Genres = append(album.Genres, domain.Genre{
|
|
ID: g.ID,
|
|
Name: g.Name,
|
|
})
|
|
}
|
|
|
|
if release != nil {
|
|
album.UPC = release.Barcode
|
|
|
|
if len(release.LabelInfo) > 0 && release.LabelInfo[0].Label != nil {
|
|
album.Label = mapLabel(release.LabelInfo[0].Label)
|
|
}
|
|
|
|
for _, m := range release.Media {
|
|
album.TotalTracks += m.TrackCount
|
|
}
|
|
album.TotalDiscs = len(release.Media)
|
|
|
|
if release.CoverArtArchive.Front {
|
|
album.CoverURL = fmt.Sprintf("https://coverartarchive.org/release/%s/front", release.ID)
|
|
}
|
|
}
|
|
|
|
return album
|
|
}
|
|
|
|
func mapTrack(mb *mbRecording, discNum, trackNum int) *domain.Track {
|
|
if mb == nil {
|
|
return nil
|
|
}
|
|
|
|
track := &domain.Track{
|
|
ID: mb.ID,
|
|
Title: mb.Title,
|
|
DurationMs: mb.Length,
|
|
DiscNumber: discNum,
|
|
TrackNumber: trackNum,
|
|
ExternalIDs: []domain.ExternalID{{
|
|
Source: "musicbrainz",
|
|
SourceID: mb.ID,
|
|
URL: fmt.Sprintf("https://musicbrainz.org/recording/%s", mb.ID),
|
|
}},
|
|
}
|
|
|
|
if len(mb.ISRCs) > 0 {
|
|
track.ISRC = mb.ISRCs[0]
|
|
}
|
|
|
|
for _, ac := range mb.ArtistCredit {
|
|
track.Artists = append(track.Artists, mapArtistCredit(&ac, "primary"))
|
|
}
|
|
|
|
for _, rel := range mb.Relations {
|
|
if rel.TargetType == "work" && rel.Work != nil {
|
|
track.Work = mapWork(rel.Work)
|
|
break
|
|
}
|
|
}
|
|
|
|
return track
|
|
}
|
|
|
|
func mapWork(mb *mbWork) *domain.Work {
|
|
if mb == nil {
|
|
return nil
|
|
}
|
|
|
|
work := &domain.Work{
|
|
ID: mb.ID,
|
|
Title: mb.Title,
|
|
Type: mb.Type,
|
|
Language: mb.Language,
|
|
}
|
|
|
|
for _, rel := range mb.Relations {
|
|
if rel.TargetType == "artist" && rel.Artist != nil {
|
|
role := "writer"
|
|
if rel.Type == "composer" || rel.Type == "lyricist" || rel.Type == "writer" {
|
|
role = rel.Type
|
|
}
|
|
work.Composers = append(work.Composers, domain.ArtistCredit{
|
|
Artist: *mapArtist(rel.Artist),
|
|
Role: role,
|
|
})
|
|
}
|
|
}
|
|
|
|
return work
|
|
}
|
|
|
|
func mapLabel(mb *mbLabel) *domain.Label {
|
|
if mb == nil {
|
|
return nil
|
|
}
|
|
|
|
return &domain.Label{
|
|
ID: mb.ID,
|
|
Name: mb.Name,
|
|
Country: mb.Country,
|
|
}
|
|
}
|
|
|
|
func mapArtistCredit(ac *mbArtistCredit, defaultRole string) domain.ArtistCredit {
|
|
credit := domain.ArtistCredit{
|
|
Role: defaultRole,
|
|
JoinPhrase: ac.JoinPhrase,
|
|
}
|
|
|
|
if ac.Artist != nil {
|
|
credit.Artist = *mapArtist(ac.Artist)
|
|
}
|
|
|
|
return credit
|
|
}
|
|
|
|
func parseDate(s string) *time.Time {
|
|
formats := []string{
|
|
"2006-01-02",
|
|
"2006-01",
|
|
"2006",
|
|
}
|
|
|
|
for _, f := range formats {
|
|
if t, err := time.Parse(f, s); err == nil {
|
|
return &t
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|