398 lines
9.3 KiB
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
|
|
}
|