package cuetools_net import ( "encoding/xml" "fmt" "git.gammaspectra.live/S.O.N.G/METANOIA/metadata" musicbrainz_org "git.gammaspectra.live/S.O.N.G/METANOIA/metadata/musicbrainz.org" "io/ioutil" "log" "net/http" "net/url" "sort" "strings" "time" ) var baseURL = "https://db.cue.tools/" var oldBaseURL = "http://db.cuetools.net/" type Source struct { client *metadata.CachingClient } func NewSource() *Source { s := &Source{} s.client = metadata.NewCachingClient(s.GetURL(), time.Second/10) return s } func (s *Source) GetName() string { return "CueTools DB" } func (s *Source) GetURL() string { return baseURL } func (s *Source) GetLicense() metadata.License { return metadata.License{ //Most core data is CC0 Code: metadata.CC0, URL: "https://cue.tools/wiki/CUETools_Database#What_about_the_license.3F", Attribution: fmt.Sprintf("%s (%s)", s.GetName(), s.GetURL()), } } func mergeByAlbumUniqueIdentifier(existing []*metadata.Album, albums ...*metadata.Album) []*metadata.Album { for _, r := range albums { if r == nil { continue } exists := false for _, m := range existing { if m.SourceUniqueIdentifier == r.SourceUniqueIdentifier { exists = true break } } if !exists { existing = append(existing, r) } } return existing } func (s *Source) FindByTocID(tocId metadata.TocID) (albums []*metadata.Album) { uri, _ := url.Parse(baseURL) uri.Path += "lookup.php" query := uri.Query() query.Add("tocid", string(tocId)) uri.RawQuery = query.Encode() response, err := s.client.Request(&http.Request{ Method: "GET", URL: uri, }, time.Hour*24*14) if err != nil { return nil } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { return nil } f := &struct { Entries []struct { Confidence int `xml:"confidence,attr"` CRC32 string `xml:"crc32,attr"` Id int `xml:"id,attr"` NPar int `xml:"npar,attr"` Stride int `xml:"stride,attr"` Parity string `xml:"parity"` TOC struct { TrackCount int `xml:"trackcount,attr"` AudioTracks int `xml:"audiotracks,attr"` FirstAudio int `xml:"firstaudio,attr"` Content string `xml:",chardata"` } `xml:"toc"` } `xml:"entry"` }{} err = xml.Unmarshal(body, f) if err == nil { //Sort highest at the top sort.SliceStable(f.Entries, func(i, j int) bool { return f.Entries[j].Confidence < f.Entries[i].Confidence }) for _, e := range f.Entries { if len(albums) > 0 && e.Confidence == 1 { break } albums = mergeByAlbumUniqueIdentifier(albums, s.FindByTOC(metadata.NewTOCFromCTDBString(e.TOC.Content))...) } } return nil } func (s *Source) FindByTOC(toc metadata.TOC) (albums []*metadata.Album) { f := s.FindMetadataByTOC(toc) if f == nil { return } tocs := make([]metadata.TOC, 0, len(f.Entries)) names := make([][]metadata.Name, len(f.Entries)) var trackCRC [][]metadata.Link for i, e := range f.Entries { tocs = append(tocs, metadata.NewTOCFromCTDB2String(e.TOC)) if len(trackCRC) <= i { trackCRC = append(trackCRC, []metadata.Link{}) } for k, c := range strings.Split(e.TrackCRC32, " ") { if len(trackCRC) <= k { trackCRC = append(trackCRC, []metadata.Link{}) } if len(trackCRC[k]) <= i { trackCRC[k] = append(trackCRC[k], metadata.Link{ Kind: "crc32", }) } trackCRC[k][i].Name = append(trackCRC[k][i].Name, metadata.Name{ Kind: "ctdb", Name: c, }) } names[i] = append(names[i], metadata.Name{ Kind: "crc32", Name: e.CRC32, }) if len(e.Syndrome) > 0 { names[i] = append(names[i], metadata.Name{ Kind: "syndrome", Name: e.Syndrome, }) } if len(e.Parity) > 0 { names[i] = append(names[i], metadata.Name{ Kind: "parity", Name: e.Parity, }) } if len(e.HasParity) > 0 { names[i] = append(names[i], metadata.Name{ Kind: "parity", Name: e.HasParity, }) } } if len(f.Metadata) > 0 { for _, e := range f.Metadata { if e.Source == "musicbrainz" { album := musicbrainz_org.NewSource().GetRelease(e.Id) if album != nil { if len(album.Discs) >= e.DiscNumber { var identifiers []metadata.Name for i := range f.Entries { identifiers = append(identifiers, metadata.Name{ Kind: "toc", Name: tocs[i].String(), }) identifiers = append(identifiers, metadata.Name{ Kind: "cddb1", Name: tocs[i].GetCDDB1().String(), }) identifiers = append(identifiers, metadata.Name{ Kind: "tocid", Name: string(tocs[i].GetTocID()), }) identifiers = append(identifiers, metadata.Name{ Kind: "discid", Name: string(tocs[i].GetDiscID()), }) identifiers = append(identifiers, names[i]...) } for ti := range album.Discs[e.DiscNumber-1].Tracks { album.Discs[e.DiscNumber-1].Tracks[ti].Links = append(album.Discs[e.DiscNumber-1].Tracks[ti].Links, trackCRC[ti]...) } album.Discs[e.DiscNumber-1].Identifiers = append(album.Discs[e.DiscNumber-1].Identifiers, identifiers...) albums = append(albums, album) } } } } if len(albums) == 0 { //TODO fallback } } return } func (s *Source) FindMetadataByTOC(toc metadata.TOC) *CueToolsMetadata { uri, _ := url.Parse(baseURL) uri.Path += "lookup2.php" query := uri.Query() query.Add("toc", toc.CTDBString()) query.Add("type", "xml") query.Add("version", "3") query.Add("fuzzy", "0") query.Add("ctdb", "1") query.Add("musicbrainz", "1") query.Add("metadata", "extensive") uri.RawQuery = query.Encode() response, err := s.client.Request(&http.Request{ Method: "GET", URL: uri, }, time.Hour*24*14) if err != nil { return nil } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { return nil } f := &CueToolsMetadata{} err = xml.Unmarshal(body, f) if err == nil { //Sort highest at the top sort.SliceStable(f.Entries, func(i, j int) bool { return f.Entries[j].Confidence < f.Entries[i].Confidence }) return f } return nil } func (s *Source) Test() { album := s.FindByTocID(metadata.NewTOCFromString("290543 150 27572 69794 109235 133506 177855 214680 252471").GetTocID()) //album := s.FindByTOC(metadata.NewTOCFromString("267453 150 24647 71194 95579 139576 174573 199089 244305")) //album := s.GetRelease("9ca2748b-88fd-44a7-bc5c-036574148571") log.Print(album) } type CueToolsMetadata struct { Entries []struct { Confidence int `xml:"confidence,attr"` CRC32 string `xml:"crc32,attr"` Id int `xml:"id,attr"` HasParity string `xml:"hasparity,attr"` NPar int `xml:"npar,attr"` Parity string `xml:"parity,attr"` Syndrome string `xml:"syndrome,attr"` Stride int `xml:"stride,attr"` TOC string `xml:"toc,attr"` TrackCRC32 string `xml:"trackcrcs,attr"` } `xml:"entry"` Metadata []struct { Album string `xml:"album,attr"` Artist string `xml:"artist,attr"` DiscCount int `xml:"disccount,attr"` DiscNumber int `xml:"discnumber,attr"` DiscName string `xml:"discname,attr"` Id string `xml:"id,attr"` Relevance int `xml:"relevance,attr"` Source string `xml:"source,attr"` Year int `xml:"year,attr"` Tracks []struct { Artist string `xml:"artist,attr"` Name string `xml:"name,attr"` } `xml:"track"` Label []struct { CatalogNumber string `xml:"catno,attr"` Name string `xml:"name,attr"` } `xml:"label"` Release struct { Country string `xml:"country,attr"` Date string `xml:"date,attr"` } `xml:"release"` CoverArt []struct { Primary bool `xml:"primary,attr"` URI string `xml:"uri,attr"` } `xml:"coverart"` } `xml:"metadata"` } func (m *CueToolsMetadata) GetTOCs() (tocs []metadata.TOC) { for _, e := range m.Entries { tocs = append(tocs, metadata.NewTOCFromCTDB2String(e.TOC)) } return } func (m *CueToolsMetadata) GetParity() (parity []string) { for _, e := range m.Entries { if len(e.HasParity) > 0 { parity = append(parity, e.HasParity) } else { parity = append(parity, e.Parity) } } return } func (m *CueToolsMetadata) GetSyndrome() (syndrome []string) { for _, e := range m.Entries { syndrome = append(syndrome, e.Syndrome) } return } func (m *CueToolsMetadata) GetCRC32() (crc32 []string) { for _, e := range m.Entries { crc32 = append(crc32, e.CRC32) } return } //GetTracksCRC32 [trackIndex][resultIndex]crc32 func (m *CueToolsMetadata) GetTracksCRC32() (crc32 [][]string) { for _, e := range m.Entries { for k, c := range strings.Split(e.TrackCRC32, " ") { if len(crc32) <= k { crc32 = append(crc32, []string{}) } crc32[k] = append(crc32[k], c) } } return } func (m *CueToolsMetadata) GetConfidence() (conf []int) { for _, e := range m.Entries { conf = append(conf, e.Confidence) } return } func (m *CueToolsMetadata) GetResultId() (id []int) { for _, e := range m.Entries { id = append(id, e.Id) } return } func (m *CueToolsMetadata) GetResultURL() (id []string) { for _, e := range m.Entries { id = append(id, fmt.Sprintf("%scd/%d", oldBaseURL, e.Id)) } return } func (m *CueToolsMetadata) GetMusicbrainzAlbums() (albums []*metadata.Album) { for _, e := range m.Metadata { if e.Source == "musicbrainz" { album := musicbrainz_org.NewSource().GetRelease(e.Id) if album != nil { albums = append(albums, album) } } } return }