METANOIA/metadata/cuetools.net/source.go

398 lines
9.3 KiB
Go

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
}