Added THwiki/vgmdb/musicbrainz/cuetools/accuraterip metadata sources
This commit is contained in:
parent
03e94ad7f4
commit
d37e263e89
105
metadata/accuraterip.com/source.go
Normal file
105
metadata/accuraterip.com/source.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package accuraterip_com
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/METANOIA/metadata"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var baseURL = "http://www.accuraterip.com/"
|
||||
|
||||
type Source struct {
|
||||
client *metadata.CachingClient
|
||||
}
|
||||
|
||||
func NewSource() *Source {
|
||||
s := &Source{}
|
||||
s.client = metadata.NewCachingClient(s.GetURL())
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Source) GetName() string {
|
||||
return "AccurateRip"
|
||||
}
|
||||
|
||||
func (s *Source) GetURL() string {
|
||||
return baseURL
|
||||
}
|
||||
|
||||
func (s *Source) GetLicense() metadata.License {
|
||||
return metadata.License{
|
||||
//Most core data is CC0
|
||||
Code: metadata.LicenseCode_NC,
|
||||
URL: "http://www.accuraterip.com/",
|
||||
Attribution: fmt.Sprintf("%s (%s)", s.GetName(), s.GetURL()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) FindMetadataByTOC(toc metadata.TOC) (results []AccurateRipMetadata) {
|
||||
uri, _ := url.Parse(baseURL)
|
||||
|
||||
var data AccurateRipDiscIdent
|
||||
data.TrackCount, data.TrackOffsetsAdded, data.TrackOffsetsMultiplied, data.CDDB1 = toc.GetAccurateRipData()
|
||||
|
||||
uri.Path += fmt.Sprintf("accuraterip/%01x/%01x/%01x/dBAR-%03d-%08x-%08x-%08x.bin", data.TrackOffsetsAdded&0xF, data.TrackOffsetsAdded>>4&0xF, data.TrackOffsetsAdded>>8&0xF, data.TrackCount, data.TrackOffsetsAdded, data.TrackOffsetsMultiplied, uint32(data.CDDB1))
|
||||
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*14)
|
||||
if err != nil || response.StatusCode != http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
var discIdent AccurateRipDiscIdent
|
||||
var discTrack AccurateRipDiscTrack
|
||||
for {
|
||||
err = binary.Read(response.Body, binary.LittleEndian, &discIdent)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
meta := AccurateRipMetadata{AccurateRipDiscIdent: discIdent}
|
||||
|
||||
for i := byte(0); i < discIdent.TrackCount; i++ {
|
||||
err = binary.Read(response.Body, binary.LittleEndian, &discTrack)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
meta.Tracks = append(meta.Tracks, discTrack)
|
||||
}
|
||||
|
||||
results = append(results, meta)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type AccurateRipDiscIdent struct {
|
||||
TrackCount byte
|
||||
TrackOffsetsAdded uint32
|
||||
TrackOffsetsMultiplied uint32
|
||||
CDDB1 metadata.CDDB1
|
||||
}
|
||||
|
||||
type AccurateRipDiscTrack struct {
|
||||
Confidence byte
|
||||
TrackCRC uint32
|
||||
OffsetFindCRC uint32
|
||||
}
|
||||
|
||||
type AccurateRipMetadata struct {
|
||||
AccurateRipDiscIdent
|
||||
Tracks []AccurateRipDiscTrack
|
||||
}
|
||||
|
||||
func (s *Source) Test() {
|
||||
meta := s.FindMetadataByTOC(metadata.NewTOCFromString("267453 150 24647 71194 95579 139576 174573 199089 244305"))
|
||||
|
||||
log.Print(meta)
|
||||
}
|
388
metadata/cuetools.net/source.go
Normal file
388
metadata/cuetools.net/source.go
Normal file
|
@ -0,0 +1,388 @@
|
|||
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())
|
||||
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.LicenseCode_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
|
||||
}
|
||||
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)
|
||||
|
||||
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 := f.GetMusicbrainzAlbums()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
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("267453 150 24647 71194 95579 139576 174573 199089 244305").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
|
||||
}
|
712
metadata/musicbrainz.org/source.go
Normal file
712
metadata/musicbrainz.org/source.go
Normal file
|
@ -0,0 +1,712 @@
|
|||
package musicbrainz_org
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/METANOIA/metadata"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var baseURL = "https://musicbrainz.org/"
|
||||
var baseAPIURL = "https://musicbrainz.org/ws/2/"
|
||||
var baseCoverAPIURL = "https://coverartarchive.org/"
|
||||
|
||||
type Source struct {
|
||||
client *metadata.CachingClient
|
||||
}
|
||||
|
||||
func NewSource() *Source {
|
||||
s := &Source{}
|
||||
s.client = metadata.NewCachingClient(s.GetURL())
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Source) GetName() string {
|
||||
return "MusicBrainz"
|
||||
}
|
||||
|
||||
func (s *Source) GetURL() string {
|
||||
return baseURL
|
||||
}
|
||||
|
||||
func (s *Source) GetLicense() metadata.License {
|
||||
return metadata.License{
|
||||
//Most core data is CC0
|
||||
Code: metadata.LicenseCode_CC_BY_NC_SA_30,
|
||||
URL: baseURL + "/doc/About/Data_License",
|
||||
Attribution: fmt.Sprintf("%s (%s)", s.GetName(), s.GetURL()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) FindByTOC(toc metadata.TOC) []*metadata.Album {
|
||||
return s.FindByDiscIDTOC(toc.GetDiscID(), toc)
|
||||
}
|
||||
|
||||
func (s *Source) FindByDiscID(discId metadata.DiscID) []*metadata.Album {
|
||||
return s.FindByDiscIDTOC(discId, metadata.TOC{})
|
||||
}
|
||||
func (s *Source) FindByDiscIDTOC(discId metadata.DiscID, toc metadata.TOC) (albums []*metadata.Album) {
|
||||
|
||||
uri, _ := url.Parse(baseAPIURL)
|
||||
uri.Path += "discid/" + string(discId)
|
||||
|
||||
query := uri.Query()
|
||||
if len(toc) > 0 {
|
||||
query.Add("toc", fmt.Sprintf("1 %s", toc.MusicBrainzString()))
|
||||
}
|
||||
query.Add("fmt", "json")
|
||||
query.Add("cdstubs", "no")
|
||||
query.Add("media-format", "all")
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*14)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Sectors int `json:"sectors"`
|
||||
Id string `json:"id"`
|
||||
Offsets []int `json:"offsets"`
|
||||
OffsetCount int `json:"offset-count"`
|
||||
Releases []struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Date string `json:"date"`
|
||||
Country string `json:"country"`
|
||||
Media []mediaEntry `json:"media"`
|
||||
} `json:"releases"`
|
||||
}
|
||||
|
||||
result := &SearchResult{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range result.Releases {
|
||||
album := s.GetRelease(r.Id)
|
||||
if album != nil {
|
||||
albums = append(albums, album)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) FindByAlbumNames(names []metadata.Name) []*metadata.Album {
|
||||
query := ""
|
||||
for _, v := range names {
|
||||
if len(query) == 0 {
|
||||
query += fmt.Sprintf("release:(%q) OR releaseaccent:(%q)", v.Name, v.Name)
|
||||
} else {
|
||||
query += fmt.Sprintf("OR release:(%q) OR releaseaccent:(%q)", v.Name, v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return s.FindQueryArguments(query)
|
||||
}
|
||||
|
||||
func (s *Source) FindByCatalogNumber(catalog metadata.CatalogNumber) []*metadata.Album {
|
||||
|
||||
query := fmt.Sprintf("catno:(%q)", string(catalog))
|
||||
|
||||
if strings.Index(string(catalog), "-") != -1 {
|
||||
query += fmt.Sprintf("OR catno:(%q)", strings.Replace(string(catalog), "-", "", -1))
|
||||
}
|
||||
|
||||
return s.FindQueryArguments(query)
|
||||
|
||||
}
|
||||
|
||||
type tagEntry struct {
|
||||
Count int `json:"count"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type ratingEntry struct {
|
||||
VotesCount int `json:"votes-count"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
type workEntry struct {
|
||||
Id string `json:"id"`
|
||||
Rating ratingEntry `json:"rating"`
|
||||
Language string `json:"language"`
|
||||
Title string `json:"title"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
Languages []string `json:"languages"`
|
||||
Attributes []string `json:"attributes"`
|
||||
Relations []relationEntry `json:"relations"`
|
||||
}
|
||||
type urlEntry struct {
|
||||
Id string `json:"id"`
|
||||
Resource string `json:"resource"`
|
||||
}
|
||||
type eventEntry struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
}
|
||||
type artistEntry struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
SortName string `json:"sort-name"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
Type string `json:"type"`
|
||||
Rating ratingEntry `json:"rating"`
|
||||
Aliases []struct {
|
||||
Name string `json:"name"`
|
||||
SortName string `json:"sort-name"`
|
||||
} `json:"aliases"`
|
||||
}
|
||||
type artistCreditEntry struct {
|
||||
Name string `json:"name"`
|
||||
Artist artistEntry `json:"artist"`
|
||||
JoinPhrase string `json:"joinphrase"`
|
||||
}
|
||||
type relationEntry struct {
|
||||
Id string `json:"id"`
|
||||
Attributes []string `json:"attributes"`
|
||||
TargetCredit string `json:"target-credit"`
|
||||
Direction string `json:"direction"`
|
||||
TargetType string `json:"target-type"`
|
||||
SourceCredit string `json:"source-credit"`
|
||||
Type string `json:"type"`
|
||||
Artist *artistEntry `json:"artist"`
|
||||
Work *workEntry `json:"work"`
|
||||
Event *eventEntry `json:"event"`
|
||||
Recording *recordingEntry `json:"recording"`
|
||||
Url *urlEntry `json:"url"`
|
||||
}
|
||||
|
||||
type recordingEntry struct {
|
||||
Id string `json:"id"`
|
||||
Video bool `json:"video"`
|
||||
Title string `json:"title"`
|
||||
Length int `json:"length"`
|
||||
FirstReleaseDate string `json:"first-release-date"`
|
||||
Rating ratingEntry `json:"rating"`
|
||||
Relations []relationEntry `json:"relations"`
|
||||
ISRCS []string `json:"isrcs"`
|
||||
ArtistCredit []artistCreditEntry `json:"artist-credit"`
|
||||
}
|
||||
|
||||
type trackEntry struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Length int `json:"length"`
|
||||
Position int `json:"position"`
|
||||
Number string `json:"number"`
|
||||
Recording recordingEntry `json:"recording"`
|
||||
}
|
||||
|
||||
type discEntry struct {
|
||||
OffsetCount int `json:"offset-count"`
|
||||
Offsets []int `json:"offsets"`
|
||||
Id string `json:"id"`
|
||||
Sectors int `json:"sectors"`
|
||||
}
|
||||
|
||||
type mediaEntry struct {
|
||||
TrackOffset int `json:"track-offset"`
|
||||
Position int `json:"position"`
|
||||
Format string `json:"format"`
|
||||
Tracks []trackEntry `json:"tracks"`
|
||||
Discs []discEntry `json:"discs"`
|
||||
TrackCount int `json:"track-count"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type labelEntry struct {
|
||||
Id string `json:"id"`
|
||||
Tags []tagEntry `json:"tags"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
Name string `json:"name"`
|
||||
ShortName string `json:"short-name"`
|
||||
//TODO Aliases
|
||||
//TODO genres
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (s *Source) GetReleaseCoverArt(releaseId string) (urls []metadata.Name) {
|
||||
uri, _ := url.Parse(baseCoverAPIURL)
|
||||
uri.Path += "release/" + releaseId
|
||||
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*60)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := &struct {
|
||||
Release string `json:"release"`
|
||||
Images []struct {
|
||||
Approved bool `json:"approved"`
|
||||
Back bool `json:"back"`
|
||||
Comment string `json:"comment"`
|
||||
Edit int `json:"edit"`
|
||||
Front bool `json:"front"`
|
||||
Id int `json:"id"`
|
||||
Image string `json:"image"`
|
||||
Thumbnails map[string]string `json:"thumbnails"`
|
||||
Types []string `json:"types"`
|
||||
} `json:"images"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err == nil {
|
||||
for _, image := range result.Images {
|
||||
//fix http -> https
|
||||
imageUrl := strings.Replace(image.Image, "http://", "https://", 1)
|
||||
name := metadata.Name{
|
||||
Kind: "",
|
||||
Name: imageUrl,
|
||||
}
|
||||
if len(image.Types) == 0 {
|
||||
name.Kind = "unknown"
|
||||
} else {
|
||||
for _, priority := range []string{
|
||||
"Other",
|
||||
"Booklet",
|
||||
"Tray",
|
||||
"Spine",
|
||||
"Obi",
|
||||
"Track",
|
||||
"Liner",
|
||||
"Sticker",
|
||||
"Medium",
|
||||
"Poster",
|
||||
"Matrix/Runout",
|
||||
"Front",
|
||||
"Back",
|
||||
"Watermark",
|
||||
"Raw/Unedited",
|
||||
} {
|
||||
for _, t := range image.Types {
|
||||
if t == priority {
|
||||
if len(name.Kind) == 0 {
|
||||
name.Kind += strings.ToLower(t)
|
||||
} else {
|
||||
name.Kind += ", " + strings.ToLower(t)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
urls = append(urls, name)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) GetRelease(releaseId string) *metadata.Album {
|
||||
uri, _ := url.Parse(baseAPIURL)
|
||||
uri.Path += "release/" + releaseId
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("fmt", "json")
|
||||
query.Add("inc", strings.Join([]string{
|
||||
"aliases",
|
||||
"artist-credits",
|
||||
"artist-rels",
|
||||
"artists",
|
||||
"discids",
|
||||
"event-rels",
|
||||
"genres",
|
||||
"isrcs",
|
||||
"labels",
|
||||
"media",
|
||||
"ratings",
|
||||
"recording-level-rels",
|
||||
"recording-rels",
|
||||
"recordings",
|
||||
"release-rels",
|
||||
"series-rels",
|
||||
"tags",
|
||||
"url-rels",
|
||||
"work-level-rels",
|
||||
"work-rels",
|
||||
}, "+"))
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*60)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
release := &struct {
|
||||
ArtistCredit []artistCreditEntry `json:"artist-credit"`
|
||||
Country string `json:"country"`
|
||||
Disambiguation string `json:"disambiguation"`
|
||||
LabelInfo []struct {
|
||||
CatalogNumber string `json:"catalog-number"`
|
||||
Label labelEntry `json:"label"`
|
||||
} `json:"label-info"`
|
||||
Relations []relationEntry `json:"relations"`
|
||||
Tags []tagEntry `json:"tags"`
|
||||
CoverArtArchive struct {
|
||||
Count int `json:"count"`
|
||||
Front bool `json:"front"`
|
||||
Darkened bool `json:"darkened"`
|
||||
Back bool `json:"back"`
|
||||
Artwork bool `json:"artwork"`
|
||||
} `json:"cover-art-archive"`
|
||||
Date string `json:"date"`
|
||||
Media []mediaEntry `json:"media"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Id string `json:"id"`
|
||||
Barcode string `json:"barcode"`
|
||||
ASIN string `json:"asin"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(body, release)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
album := &metadata.Album{
|
||||
License: s.GetLicense(),
|
||||
SourceUniqueIdentifier: baseURL + "release/" + release.Id,
|
||||
Name: []metadata.Name{
|
||||
{Kind: "original", Name: release.Title},
|
||||
},
|
||||
Identifiers: []metadata.Name{
|
||||
{
|
||||
Kind: "url",
|
||||
Name: baseURL + "release/" + release.Id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if release.CoverArtArchive.Count > 0 {
|
||||
album.Art = s.GetReleaseCoverArt(release.Id)
|
||||
}
|
||||
|
||||
album.ReleaseDate, _ = time.ParseInLocation("2006-01-02", release.Date, time.UTC)
|
||||
|
||||
for _, l := range release.LabelInfo {
|
||||
album.Identifiers = append(album.Identifiers, metadata.Name{
|
||||
Kind: "catalog",
|
||||
Name: l.CatalogNumber,
|
||||
})
|
||||
}
|
||||
|
||||
if len(release.Barcode) > 0 {
|
||||
album.Identifiers = append(album.Identifiers, metadata.Name{
|
||||
Kind: "barcode",
|
||||
Name: release.Barcode,
|
||||
})
|
||||
}
|
||||
|
||||
if len(release.ASIN) > 0 {
|
||||
album.Identifiers = append(album.Identifiers, metadata.Name{
|
||||
Kind: "asin",
|
||||
Name: release.ASIN,
|
||||
})
|
||||
}
|
||||
|
||||
for _, t := range release.Tags {
|
||||
album.Tags = append(album.Tags, metadata.Name{
|
||||
Kind: "genre",
|
||||
Name: t.Name,
|
||||
})
|
||||
}
|
||||
|
||||
for _, relation := range release.Relations {
|
||||
if relation.Direction == "backward" {
|
||||
if relation.Artist != nil {
|
||||
role := metadata.Role{
|
||||
Kind: relation.Type, //TODO: normalize
|
||||
}
|
||||
|
||||
if relation.Artist.Type == "Group" {
|
||||
role.Group = relation.Artist.Name
|
||||
}
|
||||
|
||||
role.Name = []metadata.Name{{Kind: "original", Name: relation.Artist.Name}}
|
||||
if relation.Artist.SortName != relation.Artist.Name {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "sort", Name: relation.Artist.SortName})
|
||||
}
|
||||
if len(relation.Artist.Disambiguation) > 0 {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "disambiguation", Name: relation.Artist.Disambiguation})
|
||||
}
|
||||
for _, n := range relation.Artist.Aliases {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "original", Name: n.Name})
|
||||
if n.SortName != n.Name {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "sort", Name: n.SortName})
|
||||
}
|
||||
}
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "url", Name: baseURL + "artist/" + relation.Artist.Id})
|
||||
|
||||
album.Roles = append(album.Roles, role)
|
||||
} else if relation.Event != nil && relation.Type == "available at" {
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: "release",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "name", Name: relation.Event.Name},
|
||||
{Kind: "url", Name: baseURL + "event/" + relation.Event.Id},
|
||||
},
|
||||
})
|
||||
}
|
||||
} else if relation.Direction == "forward" {
|
||||
if relation.Url != nil {
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: relation.Type,
|
||||
Name: []metadata.Name{{Kind: "url", Name: relation.Url.Resource}},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, media := range release.Media {
|
||||
disc := metadata.Disc{}
|
||||
|
||||
if len(media.Title) > 0 {
|
||||
disc.Name = append(disc.Name, metadata.Name{Kind: "original", Name: media.Title})
|
||||
}
|
||||
|
||||
if len(media.Discs) > 0 {
|
||||
d := media.Discs[0]
|
||||
|
||||
if len(d.Offsets) > 0 {
|
||||
toc := append(metadata.TOC{d.Sectors}, d.Offsets...)
|
||||
|
||||
disc.Identifiers = append(disc.Identifiers, metadata.Name{
|
||||
Kind: "toc",
|
||||
Name: toc.String(),
|
||||
})
|
||||
|
||||
disc.Identifiers = append(disc.Identifiers, metadata.Name{
|
||||
Kind: "cddb1",
|
||||
Name: toc.GetCDDB1().String(),
|
||||
})
|
||||
|
||||
disc.Identifiers = append(disc.Identifiers, metadata.Name{
|
||||
Kind: "tocid",
|
||||
Name: string(toc.GetTocID()),
|
||||
})
|
||||
}
|
||||
|
||||
if len(d.Id) > 0 {
|
||||
disc.Identifiers = append(disc.Identifiers, metadata.Name{
|
||||
Kind: "discid",
|
||||
Name: d.Id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range media.Tracks {
|
||||
track := metadata.Track{
|
||||
Name: []metadata.Name{
|
||||
{Kind: "original", Name: t.Title},
|
||||
{Kind: "url", Name: baseURL + "track/" + t.Id},
|
||||
{Kind: "url", Name: baseURL + "recording/" + t.Recording.Id},
|
||||
},
|
||||
Duration: time.Millisecond * time.Duration(t.Length),
|
||||
}
|
||||
|
||||
for _, isrc := range t.Recording.ISRCS {
|
||||
track.Name = append(track.Name, metadata.Name{
|
||||
Kind: "isrc",
|
||||
Name: isrc,
|
||||
})
|
||||
}
|
||||
|
||||
for _, relation := range t.Recording.Relations {
|
||||
if relation.Direction == "backward" {
|
||||
if relation.Artist != nil {
|
||||
role := metadata.Role{
|
||||
Kind: relation.Type, //TODO: normalize
|
||||
}
|
||||
|
||||
if relation.Artist.Type == "Group" {
|
||||
role.Group = relation.Artist.Name
|
||||
}
|
||||
|
||||
if len(relation.Attributes) > 0 {
|
||||
role.Kind += "; " + strings.Join(relation.Attributes, ", ")
|
||||
}
|
||||
|
||||
role.Name = []metadata.Name{{Kind: "original", Name: relation.Artist.Name}}
|
||||
if relation.Artist.SortName != relation.Artist.Name {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "sort", Name: relation.Artist.SortName})
|
||||
}
|
||||
if len(relation.Artist.Disambiguation) > 0 {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "disambiguation", Name: relation.Artist.Disambiguation})
|
||||
}
|
||||
for _, n := range relation.Artist.Aliases {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "original", Name: n.Name})
|
||||
if n.SortName != n.Name {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "sort", Name: n.SortName})
|
||||
}
|
||||
}
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "url", Name: baseURL + "artist/" + relation.Artist.Id})
|
||||
|
||||
track.Roles = append(track.Roles, role)
|
||||
}
|
||||
} else if relation.Direction == "forward" {
|
||||
if relation.Work != nil && relation.Type == "performance" {
|
||||
link := metadata.Link{
|
||||
Kind: "work",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "original", Name: relation.Work.Title},
|
||||
{Kind: "url", Name: baseURL + "work/" + relation.Work.Id},
|
||||
},
|
||||
}
|
||||
if len(relation.Work.Disambiguation) > 0 {
|
||||
link.Name = append(link.Name, metadata.Name{Kind: "disambiguation", Name: relation.Work.Disambiguation})
|
||||
}
|
||||
track.Links = append(track.Links, link)
|
||||
|
||||
for _, r2 := range relation.Work.Relations {
|
||||
if r2.Direction == "backward" {
|
||||
if r2.Artist != nil {
|
||||
role := metadata.Role{
|
||||
Kind: r2.Type, //TODO: normalize
|
||||
}
|
||||
|
||||
if r2.Artist.Type == "Group" {
|
||||
role.Group = r2.Artist.Name
|
||||
}
|
||||
|
||||
role.Name = []metadata.Name{{Kind: "original", Name: r2.Artist.Name}}
|
||||
if r2.Artist.SortName != r2.Artist.Name {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "sort", Name: r2.Artist.SortName})
|
||||
}
|
||||
if len(r2.Artist.Disambiguation) > 0 {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "disambiguation", Name: r2.Artist.Disambiguation})
|
||||
}
|
||||
for _, n := range r2.Artist.Aliases {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "original", Name: n.Name})
|
||||
if n.SortName != n.Name {
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "sort", Name: n.SortName})
|
||||
}
|
||||
}
|
||||
role.Name = append(role.Name, metadata.Name{Kind: "url", Name: baseURL + "artist/" + r2.Artist.Id})
|
||||
|
||||
track.Roles = append(track.Roles, role)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if relation.Recording != nil && relation.Type == "remix" {
|
||||
track.Links = append(track.Links, metadata.Link{
|
||||
Kind: "original",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "original", Name: relation.Recording.Title},
|
||||
{Kind: "url", Name: baseURL + "recording/" + relation.Recording.Id},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disc.Tracks = append(disc.Tracks, track)
|
||||
}
|
||||
|
||||
album.Discs = append(album.Discs, disc)
|
||||
|
||||
}
|
||||
|
||||
return album
|
||||
}
|
||||
|
||||
func (s *Source) FindQueryArguments(queryArgs string) (albums []*metadata.Album) {
|
||||
|
||||
uri, _ := url.Parse(baseAPIURL)
|
||||
uri.Path += "release/"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("query", queryArgs)
|
||||
query.Add("fmt", "json")
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*14)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Count int `json:"count"`
|
||||
Releases []struct {
|
||||
Id string `json:"id"`
|
||||
Score int `json:"score"`
|
||||
Count int `json:"count"`
|
||||
Title string `json:"title"`
|
||||
Date string `json:"date"`
|
||||
Country string `json:"country"`
|
||||
LabelInfo []struct {
|
||||
CatalogNumber string `json:"catalog-number"`
|
||||
Label struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"label"`
|
||||
} `json:"label-info"`
|
||||
TrackCount int `json:"track-count"`
|
||||
Media []struct {
|
||||
Format string `json:"format"`
|
||||
DiscCount int `json:"disc-count"`
|
||||
TrackCount int `json:"track-count"`
|
||||
} `json:"media"`
|
||||
} `json:"releases"`
|
||||
}
|
||||
|
||||
result := &SearchResult{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range result.Releases {
|
||||
album := s.GetRelease(r.Id)
|
||||
if album != nil {
|
||||
albums = append(albums, album)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) Test() {
|
||||
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)
|
||||
}
|
891
metadata/thwiki.cc/source.go
Normal file
891
metadata/thwiki.cc/source.go
Normal file
|
@ -0,0 +1,891 @@
|
|||
package thwiki_cc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/METANOIA/metadata"
|
||||
"git.gammaspectra.live/S.O.N.G/METANOIA/utilities/wiki"
|
||||
"github.com/oriser/regroup"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var baseURL = "https://thwiki.cc/"
|
||||
var baseLyricsURL = "https://cd.thwiki.cc/"
|
||||
|
||||
type Source struct {
|
||||
client *metadata.CachingClient
|
||||
}
|
||||
|
||||
func NewSource() *Source {
|
||||
s := &Source{}
|
||||
s.client = metadata.NewCachingClient(s.GetURL())
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Source) GetName() string {
|
||||
return "THBWiki"
|
||||
}
|
||||
|
||||
func (s *Source) GetURL() string {
|
||||
return baseURL
|
||||
}
|
||||
|
||||
func (s *Source) GetLicense() metadata.License {
|
||||
return metadata.License{
|
||||
Code: metadata.LicenseCode_CC_BY_NC_SA_30,
|
||||
URL: baseURL + "/THBWiki:关于#.E7.89.88.E6.9D.83.E4.BF.A1.E6.81.AF",
|
||||
Attribution: fmt.Sprintf("%s (%s)", s.GetName(), s.GetURL()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) FindByTOC(toc metadata.TOC) []*metadata.Album {
|
||||
return s.FindByCDDB1Group([]metadata.CDDB1{toc.GetCDDB1()})
|
||||
}
|
||||
|
||||
func (s *Source) FindByCDDB1(cddb metadata.CDDB1) []*metadata.Album {
|
||||
return s.FindByCDDB1Group([]metadata.CDDB1{cddb})
|
||||
}
|
||||
|
||||
func (s *Source) FindByCDDB1Group(group []metadata.CDDB1) []*metadata.Album {
|
||||
|
||||
tracks := 0
|
||||
var duration time.Duration
|
||||
discs := len(group)
|
||||
|
||||
secondsRange := 1.
|
||||
|
||||
gapLength := time.Second * 2
|
||||
|
||||
for _, cddb := range group {
|
||||
duration += cddb.GetDuration()
|
||||
tracks += cddb.GetTrackNumber()
|
||||
|
||||
offset := (gapLength * time.Duration(cddb.GetTrackNumber())) / 2 //pre-gap length
|
||||
duration -= offset
|
||||
secondsRange += offset.Seconds()
|
||||
}
|
||||
|
||||
query := []string{
|
||||
fmt.Sprintf("G3%d\t%d", discs, discs),
|
||||
fmt.Sprintf("H3%d\t%d", tracks, tracks),
|
||||
fmt.Sprintf("I3%d\t%d", int(math.Floor(duration.Seconds()-secondsRange)), int(math.Ceil(duration.Seconds()+secondsRange))),
|
||||
}
|
||||
|
||||
return s.FindAdvancedQueryArguments(strings.Join(query, "\n") + "\n")
|
||||
}
|
||||
|
||||
func (s *Source) FindByAlbumNames(names []metadata.Name) []*metadata.Album {
|
||||
srsearch := ""
|
||||
for _, v := range names {
|
||||
if len(srsearch) > 0 {
|
||||
srsearch += fmt.Sprintf(" OR %q OR %q", "名称 = "+v.Name, "译名 = "+v.Name)
|
||||
} else {
|
||||
srsearch += fmt.Sprintf("%q OR %q", "名称 = "+v.Name, "译名 = "+v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return s.FindQueryArguments(srsearch)
|
||||
}
|
||||
|
||||
func (s *Source) FindByCatalogNumber(catalog metadata.CatalogNumber) []*metadata.Album {
|
||||
srsearch := fmt.Sprintf("%q", "编号 = "+catalog)
|
||||
if strings.Index(string(catalog), "-") != -1 {
|
||||
srsearch += fmt.Sprintf(" OR %q", "编号 = "+strings.Replace(string(catalog), "-", "", -1))
|
||||
}
|
||||
|
||||
return s.FindQueryArguments(srsearch)
|
||||
|
||||
}
|
||||
|
||||
func (s *Source) FindQueryArguments(queryArgs string) (albums []*metadata.Album) {
|
||||
|
||||
uri, _ := url.Parse(baseURL)
|
||||
uri.Path += "api.php"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("action", "query")
|
||||
query.Add("format", "json")
|
||||
query.Add("list", "search")
|
||||
query.Add("srwhat", "text")
|
||||
query.Add("srlimit", "500")
|
||||
|
||||
srsearch := "incategory:同人专辑 ("
|
||||
srsearch += queryArgs
|
||||
srsearch += ")"
|
||||
|
||||
query.Add("srsearch", srsearch)
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*14)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Query struct {
|
||||
SearchInfo struct {
|
||||
TotalHits int `json:"totalhits"`
|
||||
} `json:"searchinfo"`
|
||||
Search []struct {
|
||||
NameSpace int `json:"ns"`
|
||||
Title string `json:"title"`
|
||||
PageId int `json:"pageid"`
|
||||
Snippet string `json:"snippet"`
|
||||
} `json:"search"`
|
||||
} `json:"query"`
|
||||
}
|
||||
|
||||
result := &SearchResult{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range result.Query.Search {
|
||||
album := s.GetAlbumInformation(wiki.NormalizeWikiTitle(r.Title))
|
||||
if album != nil {
|
||||
albums = append(albums, album)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) FindAdvancedQueryArguments(queryArgs string) (albums []*metadata.Album) {
|
||||
|
||||
uri, _ := url.Parse(baseURL)
|
||||
uri.Path += "api.php"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("action", "uask")
|
||||
query.Add("pre", "专辑")
|
||||
query.Add("sort", "")
|
||||
query.Add("query", queryArgs)
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*30)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
split := strings.Split(string(body), " ")
|
||||
|
||||
if len(split) != 2 {
|
||||
return nil
|
||||
}
|
||||
numberOfResults, _ := strconv.Atoi(split[0])
|
||||
|
||||
if numberOfResults == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query.Add("token", split[1])
|
||||
query.Add("order", "")
|
||||
query.Add("limit", "25")
|
||||
query.Add("offset", "0")
|
||||
query.Add("result", "d")
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err = s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*30)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
From int `json:"from"`
|
||||
Till int `json:"till"`
|
||||
Total int `json:"total"`
|
||||
Symbols string `json:"symbols"`
|
||||
Results struct {
|
||||
Link []string `json:"link"`
|
||||
Text []string `json:"text"`
|
||||
} `json:"resu"`
|
||||
}
|
||||
|
||||
result := &SearchResult{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, albumName := range result.Results.Text {
|
||||
album := s.GetAlbumInformation(wiki.NormalizeWikiTitle(albumName))
|
||||
if album != nil {
|
||||
albums = append(albums, album)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) GetArticle(title string) ([]interface{}, error) {
|
||||
uri, _ := url.Parse(baseURL)
|
||||
uri.Path += "index.php"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("title", title)
|
||||
query.Add("action", "raw")
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*60)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wiki.ParseWikiText(string(body)), nil
|
||||
}
|
||||
|
||||
func (s *Source) GetFileURL(title string) string {
|
||||
uri, _ := url.Parse(baseURL)
|
||||
uri.Path += "api.php"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("titles", "File:"+title)
|
||||
query.Add("action", "query")
|
||||
query.Add("prop", "imageinfo")
|
||||
query.Add("iiprop", "url")
|
||||
query.Add("format", "json")
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*180)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
type ImageData struct {
|
||||
Query struct {
|
||||
Pages map[string]struct {
|
||||
ImageInfo []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"imageinfo"`
|
||||
} `json:"pages"`
|
||||
} `json:"query"`
|
||||
}
|
||||
|
||||
var d ImageData
|
||||
|
||||
err = json.Unmarshal(body, &d)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, v := range d.Query.Pages {
|
||||
if len(v.ImageInfo) > 0 {
|
||||
return v.ImageInfo[0].URL
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Source) GetSongLyrics(songName string) *metadata.Lyrics {
|
||||
uri, _ := url.Parse(baseLyricsURL)
|
||||
uri.Path += "lyrics/" + wiki.NormalizeWikiTitle(songName) + ".lrc"
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*120)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var lineRE = regroup.MustCompile(`(?m)^\[(?P<time_minutes>[0-9]+):(?P<time_seconds>[0-9]+)\.(?P<time_decimals>[0-9]+)\](?P<line>.*)$`)
|
||||
var lineMetaRE = regroup.MustCompile(`(?m)^\[(?P<kind>(ti|al|ar|re)):(?P<line>.*)\]$`)
|
||||
|
||||
type LineMatch struct {
|
||||
TimeMinutes int `regroup:"time_minutes"`
|
||||
TimeSeconds int `regroup:"time_seconds"`
|
||||
TimeDecimals string `regroup:"time_decimals"`
|
||||
Line string `regroup:"line"`
|
||||
}
|
||||
type LineMetaMatch struct {
|
||||
Kind string `regroup:"kind"`
|
||||
Line string `regroup:"line"`
|
||||
}
|
||||
m := &LineMatch{}
|
||||
rets, err := lineRE.MatchAllToTarget(string(body), -1, m)
|
||||
if err == nil {
|
||||
|
||||
lyrics := &metadata.Lyrics{
|
||||
Identifiers: []metadata.Name{
|
||||
{Kind: "url", Name: baseURL + "歌词:" + wiki.NormalizeWikiTitle(songName)},
|
||||
},
|
||||
}
|
||||
|
||||
m2 := &LineMetaMatch{}
|
||||
rets2, _ := lineMetaRE.MatchAllToTarget(string(body), -1, m2)
|
||||
for _, elem2 := range rets2 {
|
||||
match2 := elem2.(*LineMetaMatch)
|
||||
|
||||
switch match2.Kind {
|
||||
case "ti":
|
||||
lyrics.Identifiers = append(lyrics.Identifiers, metadata.Name{
|
||||
Kind: "title",
|
||||
Name: strings.TrimSpace(match2.Line),
|
||||
})
|
||||
case "al":
|
||||
lyrics.Identifiers = append(lyrics.Identifiers, metadata.Name{
|
||||
Kind: "album",
|
||||
Name: strings.TrimSpace(match2.Line),
|
||||
})
|
||||
case "ar":
|
||||
lyrics.Identifiers = append(lyrics.Identifiers, metadata.Name{
|
||||
Kind: "artist",
|
||||
Name: strings.TrimSpace(match2.Line),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, elem := range rets {
|
||||
match := elem.(*LineMatch)
|
||||
|
||||
var last *metadata.LyricTextEntry
|
||||
if len(lyrics.Entries) > 0 {
|
||||
last = &lyrics.Entries[len(lyrics.Entries)-1]
|
||||
}
|
||||
|
||||
start := time.Minute*time.Duration(match.TimeMinutes) + time.Second*time.Duration(match.TimeSeconds)
|
||||
if len(match.TimeDecimals) == 1 {
|
||||
d, _ := strconv.Atoi(match.TimeDecimals)
|
||||
start += time.Millisecond * time.Duration(d) * 100
|
||||
} else if len(match.TimeDecimals) == 2 {
|
||||
d, _ := strconv.Atoi(match.TimeDecimals)
|
||||
start += time.Millisecond * time.Duration(d) * 10
|
||||
} else if len(match.TimeDecimals) == 3 {
|
||||
d, _ := strconv.Atoi(match.TimeDecimals)
|
||||
start += time.Millisecond * time.Duration(d)
|
||||
} else {
|
||||
//TODO
|
||||
}
|
||||
|
||||
if last != nil {
|
||||
last.End = start
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(match.Line)
|
||||
|
||||
if len(line) > 0 {
|
||||
lyrics.Entries = append(lyrics.Entries, metadata.LyricTextEntry{
|
||||
Start: start,
|
||||
End: -1,
|
||||
Content: []metadata.Name{
|
||||
{Kind: "japanese", Name: line},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return lyrics
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Source) GetAlbumInformation(title string) *metadata.Album {
|
||||
article, err := s.GetArticle(title)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var album *metadata.Album
|
||||
|
||||
normalizeStringCharacters := func(text string) string {
|
||||
for _, v := range [][2]string{
|
||||
{",", ", "},
|
||||
{"(", "("},
|
||||
{")", ")"},
|
||||
} {
|
||||
text = strings.Replace(text, v[0], v[1], -1)
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
removeLinks := func(text string) (result string) {
|
||||
|
||||
index := 0
|
||||
var isInternal bool
|
||||
|
||||
for i := 0; i < len(text); i++ {
|
||||
c := text[i]
|
||||
|
||||
if index != 0 {
|
||||
if c == ']' {
|
||||
|
||||
if isInternal {
|
||||
segments := strings.Split(text[index:i], "|")
|
||||
if len(segments) > 1 {
|
||||
segments = segments[1:]
|
||||
}
|
||||
result += strings.Join(segments, "|")
|
||||
} else {
|
||||
segments := strings.Split(text[index:i], " ")
|
||||
if len(segments) > 1 {
|
||||
segments = segments[1:]
|
||||
}
|
||||
result += strings.Join(segments, " ")
|
||||
}
|
||||
|
||||
if len(text) > i+1 && text[i+1] == ']' {
|
||||
i++
|
||||
}
|
||||
|
||||
index = 0
|
||||
}
|
||||
} else {
|
||||
if c == '[' {
|
||||
if len(text) > i+1 && text[i+1] == '[' {
|
||||
i++
|
||||
isInternal = true
|
||||
} else {
|
||||
isInternal = false
|
||||
}
|
||||
|
||||
index = i + 1
|
||||
} else {
|
||||
result += text[i : i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
getStringValue := func(v []interface{}) (result []string) {
|
||||
|
||||
for _, value := range v {
|
||||
text, ok := value.(string)
|
||||
if ok {
|
||||
result = append(result, normalizeStringCharacters(text))
|
||||
} else {
|
||||
template, ok := value.(*wiki.Template)
|
||||
if ok {
|
||||
switch template.Name {
|
||||
case "PAGENAME":
|
||||
fallthrough
|
||||
case "SUBPAGENAME":
|
||||
result = append(result, title)
|
||||
case "CM":
|
||||
result = append(result, fmt.Sprintf("Comiket %s", template.Parameters["1"][0].(string)))
|
||||
case "红楼梦":
|
||||
result = append(result, fmt.Sprintf("Touhou Kouroumu %s", template.Parameters["1"][0].(string)))
|
||||
case "例大祭":
|
||||
result = append(result, fmt.Sprintf("Hakurei Shrine Reitaisai %s", template.Parameters["1"][0].(string)))
|
||||
default:
|
||||
result = append(result, template.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var staffMappings []map[string]string
|
||||
listingStaff := false
|
||||
var staffRE = regroup.MustCompile(`(?m)^;(?P<position>[^:]+):(?P<name>.+)$`)
|
||||
var staffNameSplitRE = regroup.MustCompile(`(?P<name>[^<]+)(?P<position><br/?>|$)`)
|
||||
var staffNameGroupRE = regroup.MustCompile(`(?P<position>.+)\((?P<name>.+)\)`)
|
||||
|
||||
var zunComposerRole = metadata.Role{
|
||||
Kind: "composer",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "original", Name: "ZUN"},
|
||||
{Kind: "url", Name: baseURL + "ZUN"},
|
||||
},
|
||||
}
|
||||
|
||||
handleStaffMapping := func(discIndex int, val []interface{}, kind string, track *metadata.Track) {
|
||||
if len(staffMappings) > discIndex {
|
||||
for _, e := range strings.Split(strings.Join(getStringValue(val), ","), ",") {
|
||||
entryValue := strings.TrimSpace(e)
|
||||
|
||||
if len(entryValue) > 0 {
|
||||
groupValue, ok := staffMappings[discIndex][entryValue]
|
||||
|
||||
if ok && groupValue != entryValue {
|
||||
track.Roles = append(track.Roles, metadata.Role{
|
||||
Kind: kind,
|
||||
Name: []metadata.Name{{Kind: "original", Name: entryValue}, {Kind: "url", Name: baseURL + wiki.NormalizeWikiTitle(entryValue)}},
|
||||
Group: groupValue,
|
||||
})
|
||||
} else {
|
||||
track.Roles = append(track.Roles, metadata.Role{
|
||||
Kind: kind,
|
||||
Name: []metadata.Name{{Kind: "original", Name: entryValue}, {Kind: "url", Name: baseURL + wiki.NormalizeWikiTitle(entryValue)}},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range article {
|
||||
text, ok := v.(string)
|
||||
if ok {
|
||||
if strings.Index(text, "== Staff ==") != -1 {
|
||||
listingStaff = true
|
||||
}
|
||||
|
||||
if listingStaff {
|
||||
type StaffMatch struct {
|
||||
Position string `regroup:"position"`
|
||||
Name string `regroup:"name"`
|
||||
}
|
||||
m := &StaffMatch{}
|
||||
rets, err := staffRE.MatchAllToTarget(normalizeStringCharacters(text), -1, m)
|
||||
if err == nil {
|
||||
for _, elem := range rets {
|
||||
match := elem.(*StaffMatch)
|
||||
|
||||
for _, roleName := range strings.Split(match.Position, "/") {
|
||||
roleName = strings.ToLower(strings.TrimSpace(roleName))
|
||||
|
||||
switch roleName {
|
||||
case "total produce":
|
||||
fallthrough
|
||||
case "produce":
|
||||
fallthrough
|
||||
case "production":
|
||||
roleName = "producer"
|
||||
case "assistant":
|
||||
roleName = "assistant"
|
||||
case "illustration":
|
||||
fallthrough
|
||||
case "cover artwork":
|
||||
roleName = "illustration"
|
||||
|
||||
case "mastering":
|
||||
roleName = "mastering"
|
||||
case "graphic design":
|
||||
fallthrough
|
||||
case "graphics":
|
||||
fallthrough
|
||||
case "design":
|
||||
roleName = "design"
|
||||
case "compose":
|
||||
roleName = "composer"
|
||||
}
|
||||
|
||||
rets2, err := staffNameSplitRE.MatchAllToTarget(removeLinks(strings.TrimSpace(match.Name)), -1, m)
|
||||
if err == nil {
|
||||
for _, elem := range rets2 {
|
||||
match2 := elem.(*StaffMatch)
|
||||
|
||||
m3 := &StaffMatch{}
|
||||
|
||||
role := metadata.Role{
|
||||
Kind: roleName,
|
||||
Name: []metadata.Name{{Kind: "original", Name: strings.TrimSpace(match2.Name)}},
|
||||
}
|
||||
|
||||
if staffNameGroupRE.MatchToTarget(strings.TrimSpace(match2.Name), m3) == nil {
|
||||
//Is a group
|
||||
role.Name = []metadata.Name{{Kind: "original", Name: strings.TrimSpace(m3.Position)}}
|
||||
role.Group = strings.TrimSpace(m3.Name)
|
||||
}
|
||||
|
||||
album.Roles = append(album.Roles, role)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
template, ok := v.(*wiki.Template)
|
||||
if ok {
|
||||
switch template.Name {
|
||||
case "专辑人员列表": //Album Person List
|
||||
if album == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
discIndex, _ := strconv.Atoi(strings.Join(getStringValue(template.Parameters["碟号"]), " ")) //disc number
|
||||
discIndex--
|
||||
|
||||
staffMappings = append(staffMappings, make(map[string]string))
|
||||
|
||||
for _, val := range template.Parameters { //nested
|
||||
for _, value := range getStringValue(val) {
|
||||
parts := strings.Split(value, "=")
|
||||
p0 := strings.TrimSpace(parts[0])
|
||||
if len(parts) > 1 {
|
||||
staffMappings[discIndex][p0] = strings.TrimSpace(parts[1])
|
||||
} else {
|
||||
staffMappings[discIndex][p0] = p0
|
||||
}
|
||||
}
|
||||
}
|
||||
case "专辑曲目列表": //Album Track List
|
||||
if album == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
listingStaff = false
|
||||
|
||||
discIndex := len(album.Discs)
|
||||
|
||||
disc := metadata.Disc{}
|
||||
|
||||
for _, value := range template.Parameters["嵌套"] { //nested
|
||||
|
||||
tpl, ok := value.(*wiki.Template)
|
||||
if ok {
|
||||
track := metadata.Track{}
|
||||
for k, val := range tpl.Parameters {
|
||||
switch k {
|
||||
case "时长": //duration
|
||||
d := getStringValue(val)[0]
|
||||
sub := strings.Split(d, ":")
|
||||
s1, _ := strconv.ParseInt(strings.TrimLeft(sub[0], "0"), 10, 0)
|
||||
if len(sub) > 2 {
|
||||
//TODO
|
||||
} else if len(sub) > 1 {
|
||||
s2, _ := strconv.ParseInt(strings.TrimLeft(sub[1], "0"), 10, 0)
|
||||
track.Duration = time.Minute*time.Duration(s1) + time.Second*time.Duration(s2)
|
||||
} else {
|
||||
track.Duration = time.Second * time.Duration(s1)
|
||||
}
|
||||
case "名称": //title
|
||||
trackTitle := strings.Join(getStringValue(val), " ")
|
||||
track.Name = append(track.Name, metadata.Name{
|
||||
Kind: "original",
|
||||
Name: trackTitle,
|
||||
})
|
||||
track.Lyrics = func() *metadata.Lyrics {
|
||||
return s.GetSongLyrics(trackTitle)
|
||||
}
|
||||
case "原专辑": //original release
|
||||
track.Links = append(track.Links, metadata.Link{
|
||||
Kind: "original release",
|
||||
Name: []metadata.Name{{Kind: "original", Name: strings.Join(getStringValue(val), " ")}},
|
||||
})
|
||||
case "原名称": //original release title
|
||||
track.Links = append(track.Links, metadata.Link{
|
||||
Kind: "original release title",
|
||||
Name: []metadata.Name{{Kind: "original", Name: strings.Join(getStringValue(val), " ")}},
|
||||
})
|
||||
case "原曲": //original song
|
||||
|
||||
case "编曲": //arranger/composer
|
||||
if _, ok := tpl.Parameters["原曲"]; ok {
|
||||
handleStaffMapping(discIndex, val, "arranger", &track)
|
||||
track.Roles = append(track.Roles, zunComposerRole) //TODO check
|
||||
} else {
|
||||
handleStaffMapping(discIndex, val, "composer", &track)
|
||||
}
|
||||
case "再编曲": //re-arranger
|
||||
handleStaffMapping(discIndex, val, "remix", &track)
|
||||
case "演唱": //vocals
|
||||
handleStaffMapping(discIndex, val, "vocals", &track)
|
||||
case "作词": //lyrics
|
||||
handleStaffMapping(discIndex, val, "lyrics", &track)
|
||||
}
|
||||
}
|
||||
|
||||
disc.Tracks = append(disc.Tracks, track)
|
||||
}
|
||||
}
|
||||
|
||||
album.Discs = append(album.Discs, disc)
|
||||
|
||||
case "同人专辑信息": //Doujin Album Information
|
||||
album = &metadata.Album{
|
||||
License: s.GetLicense(),
|
||||
SourceUniqueIdentifier: baseURL + title,
|
||||
Identifiers: []metadata.Name{
|
||||
{
|
||||
Kind: "url",
|
||||
Name: baseURL + title,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
album.Identifiers = append(album.Identifiers)
|
||||
|
||||
for k, val := range template.Parameters {
|
||||
|
||||
switch k {
|
||||
case "封面": //cover: jpg
|
||||
fileName := strings.Join(getStringValue(val), "")
|
||||
if strings.Index(fileName, ".") == -1 {
|
||||
fileName = title + "封面." + fileName
|
||||
}
|
||||
f := s.GetFileURL(fileName)
|
||||
if len(f) > 0 {
|
||||
album.Art = append(album.Art, metadata.Name{
|
||||
Kind: "front",
|
||||
Name: s.GetFileURL(fileName),
|
||||
})
|
||||
}
|
||||
|
||||
case "展会": //release
|
||||
for _, value := range val {
|
||||
tpl, ok := value.(*wiki.Template)
|
||||
if ok {
|
||||
switch tpl.Name {
|
||||
case "CM":
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: "release",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "name", Name: fmt.Sprintf("Comiket %s", tpl.Parameters["1"][0].(string))},
|
||||
},
|
||||
})
|
||||
case "红楼梦":
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: "release",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "name", Name: fmt.Sprintf("Touhou Kouroumu %s", tpl.Parameters["1"][0].(string))},
|
||||
},
|
||||
})
|
||||
case "例大祭":
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: "release",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "name", Name: fmt.Sprintf("Hakurei Shrine Reitaisai %s", tpl.Parameters["1"][0].(string))},
|
||||
},
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
case "名称": //name
|
||||
album.Name = append(album.Name, metadata.Name{
|
||||
Kind: "original",
|
||||
Name: strings.Join(getStringValue(val), " "),
|
||||
})
|
||||
|
||||
case "制作方": //produced by
|
||||
for _, producer := range strings.Split(strings.Join(getStringValue(val), ","), ",") {
|
||||
album.Roles = append(album.Roles, metadata.Role{
|
||||
Kind: "producer",
|
||||
Name: []metadata.Name{{Kind: "original", Name: producer}, {Kind: "url", Name: baseURL + wiki.NormalizeWikiTitle(producer)}},
|
||||
})
|
||||
}
|
||||
|
||||
case "发行方": //issuer
|
||||
for _, issuer := range strings.Split(strings.Join(getStringValue(val), ","), ",") {
|
||||
album.Roles = append(album.Roles, metadata.Role{
|
||||
Kind: "issuer",
|
||||
Name: []metadata.Name{{Kind: "original", Name: issuer}, {Kind: "url", Name: baseURL + wiki.NormalizeWikiTitle(issuer)}},
|
||||
})
|
||||
}
|
||||
|
||||
case "编号": //catalog number
|
||||
|
||||
for _, catalog := range strings.Split(strings.Join(getStringValue(val), "+"), "+") {
|
||||
album.Identifiers = append(album.Identifiers, metadata.Name{
|
||||
Kind: "catalog",
|
||||
Name: catalog,
|
||||
})
|
||||
}
|
||||
|
||||
case "风格类型": //style
|
||||
for _, style := range strings.Split(strings.Join(getStringValue(val), ","), ",") {
|
||||
switch strings.ToLower(style) {
|
||||
default:
|
||||
album.Tags = append(album.Tags, metadata.Name{
|
||||
Kind: "genre",
|
||||
Name: strings.ToLower(style), //TODO: normalize
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case "官网页面": //official page
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: "official",
|
||||
Name: []metadata.Name{{Kind: "url", Name: strings.Join(getStringValue(val), " ")}},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//unknown value
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return album
|
||||
}
|
||||
|
||||
func (s *Source) Test() {
|
||||
albumC := s.FindByCDDB1(0x730dec08)
|
||||
|
||||
albums := s.FindByAlbumNames([]metadata.Name{{Name: "Bayside Beat"}})
|
||||
|
||||
album := s.GetAlbumInformation("Haunted_House")
|
||||
album2 := s.GetAlbumInformation("マジコカタストロフィ")
|
||||
|
||||
lyrics := album2.Discs[0].Tracks[1].Lyrics()
|
||||
|
||||
log.Print(album)
|
||||
log.Print(albumC)
|
||||
log.Print(album2)
|
||||
log.Print(lyrics)
|
||||
log.Print(albums)
|
||||
}
|
581
metadata/vgmdb.net/source.go
Normal file
581
metadata/vgmdb.net/source.go
Normal file
|
@ -0,0 +1,581 @@
|
|||
package vgmdb_net
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/METANOIA/metadata"
|
||||
"git.gammaspectra.live/S.O.N.G/METANOIA/utilities"
|
||||
"github.com/oriser/regroup"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var baseURL = "https://vgmdb.net/"
|
||||
var baseAPIURL = "https://vgmdb.info/"
|
||||
|
||||
type Source struct {
|
||||
client *metadata.CachingClient
|
||||
}
|
||||
|
||||
func NewSource() *Source {
|
||||
s := &Source{}
|
||||
s.client = metadata.NewCachingClient(s.GetURL())
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Source) GetName() string {
|
||||
return "VGMdb"
|
||||
}
|
||||
|
||||
func (s *Source) GetURL() string {
|
||||
return baseURL
|
||||
}
|
||||
|
||||
func (s *Source) GetLicense() metadata.License {
|
||||
return metadata.License{
|
||||
Code: metadata.LicenseCode_Unknown,
|
||||
URL: "",
|
||||
Attribution: fmt.Sprintf("%s (%s)", s.GetName(), s.GetURL()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) FindByTOC(toc metadata.TOC) (results []*metadata.Album) {
|
||||
uri, _ := url.Parse(baseURL)
|
||||
uri.Path += "cddb"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("cmd", fmt.Sprintf("cddb query %s", toc.CDDBString()))
|
||||
query.Add("hello", "anonymous localhost METANOIA "+utilities.Version)
|
||||
query.Add("proto", "6")
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*14)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
matchLineRE := regroup.MustCompile(`(?m)^(200 )?(?P<group>[A-Za-z][A-Za-z0-9]+) (?P<cddb1>[a-f0-9]{8}) (?P<match>.+)$`)
|
||||
matchURLRE := regroup.MustCompile(`(?m)^EXTD=https://vgmdb.net/album/(?P<album>[0-9]+)$`)
|
||||
type matchResult struct {
|
||||
Group string `regroup:"group"`
|
||||
CDDB1 string `regroup:"cddb1"`
|
||||
Match string `regroup:"match"`
|
||||
}
|
||||
m := &matchResult{}
|
||||
rets, err := matchLineRE.MatchAllToTarget(string(body), -1, m)
|
||||
if err == nil {
|
||||
|
||||
for _, elem := range rets {
|
||||
match := elem.(*matchResult)
|
||||
|
||||
uri, _ := url.Parse(baseURL)
|
||||
uri.Path += "cddb"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("cmd", fmt.Sprintf("cddb read %s %s", match.Group, match.CDDB1))
|
||||
query.Add("hello", "anonymous localhost METANOIA "+utilities.Version)
|
||||
query.Add("proto", "6")
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*30)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t := &struct {
|
||||
AlbumId int `regroup:"album"`
|
||||
}{}
|
||||
if matchURLRE.MatchToTarget(string(body), t) == nil {
|
||||
results = mergeByAlbumUniqueIdentifier(results, s.GetAlbumInformation(t.AlbumId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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) FindByAlbumNames(names []metadata.Name) (results []*metadata.Album) {
|
||||
|
||||
for _, v := range names {
|
||||
results = mergeByAlbumUniqueIdentifier(results, s.FindQueryArguments(v.Name)...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) FindByCatalogNumber(catalog metadata.CatalogNumber) (results []*metadata.Album) {
|
||||
results = s.FindQueryArguments(string(catalog))
|
||||
if strings.Index(string(catalog), "-") != -1 {
|
||||
results = mergeByAlbumUniqueIdentifier(results, s.FindQueryArguments(strings.Replace(string(catalog), "-", "", -1))...)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
type namesEntry struct {
|
||||
English string `json:"en"`
|
||||
Japanese string `json:"ja"`
|
||||
Romaji string `json:"ja-latn"`
|
||||
}
|
||||
type titlesEntry struct {
|
||||
English string `json:"English"`
|
||||
Japanese string `json:"Japanese"`
|
||||
Romaji string `json:"Romaji"`
|
||||
}
|
||||
|
||||
type entityEntry struct {
|
||||
Link string `json:"link"`
|
||||
Names namesEntry `json:"names"`
|
||||
}
|
||||
|
||||
type imageEntry struct {
|
||||
Name string `json:"name"`
|
||||
Full string `json:"full"`
|
||||
Medium string `json:"medium"`
|
||||
Thumb string `json:"thumb"`
|
||||
}
|
||||
|
||||
type trackEntry struct {
|
||||
Names titlesEntry `json:"names"`
|
||||
Duration string `json:"track_length"`
|
||||
}
|
||||
|
||||
type discEntry struct {
|
||||
Length string `json:"disc_length"`
|
||||
Name string `json:"name"`
|
||||
Tracks []trackEntry `json:"tracks"`
|
||||
}
|
||||
|
||||
func (s *Source) FindQueryArguments(queryArgs string) (albums []*metadata.Album) {
|
||||
|
||||
uri, _ := url.Parse(baseAPIURL)
|
||||
uri.Path += "search/albums"
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("q", queryArgs)
|
||||
query.Add("format", "json")
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*14)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Link string `json:"link"`
|
||||
Meta map[string]interface{} `json:"meta"`
|
||||
Query string `json:"query"`
|
||||
Results struct {
|
||||
Albums []struct {
|
||||
Catalog string `json:"catalog"`
|
||||
Category string `json:"category"`
|
||||
Link string `json:"link"`
|
||||
MediaFormat string `json:"media_format"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
Titles namesEntry `json:"titles"`
|
||||
} `json:"albums"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
result := &SearchResult{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range result.Results.Albums {
|
||||
|
||||
parts := strings.Split(r.Link, "/")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
albumId, _ := strconv.Atoi(parts[1])
|
||||
if albumId == 0 {
|
||||
continue
|
||||
}
|
||||
album := s.GetAlbumInformation(albumId)
|
||||
if album != nil {
|
||||
albums = append(albums, album)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Source) GetAlbumInformation(id int) *metadata.Album {
|
||||
uri, _ := url.Parse(baseAPIURL)
|
||||
uri.Path += fmt.Sprintf("album/%d", id)
|
||||
|
||||
query := uri.Query()
|
||||
query.Add("format", "json")
|
||||
uri.RawQuery = query.Encode()
|
||||
response, err := s.client.Request(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}, time.Hour*24*60)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := &struct {
|
||||
Arrangers []entityEntry `json:"arrangers"`
|
||||
Catalog string `json:"catalog"`
|
||||
Categories []string `json:"categories"`
|
||||
Category string `json:"category"`
|
||||
Classification string `json:"classification"`
|
||||
Composers []entityEntry `json:"composers"`
|
||||
Covers []imageEntry `json:"covers"`
|
||||
Discs []discEntry `json:"discs"`
|
||||
Link string `json:"link"`
|
||||
Lyricists []entityEntry `json:"lyricists"`
|
||||
MediaFormat string `json:"media_format"`
|
||||
Meta struct {
|
||||
AddedDate string `json:"added_date"`
|
||||
EditedDate string `json:"edited_date"`
|
||||
FetchedDate string `json:"fetched_date"`
|
||||
TTL int `json:"ttl"`
|
||||
Visitors int `json:"visitors"`
|
||||
}
|
||||
Name string `json:"name"`
|
||||
Names namesEntry `json:"names"`
|
||||
Notes string `json:"notes"`
|
||||
Organizations []struct {
|
||||
Link string `json:"link"`
|
||||
Role string `json:"role"`
|
||||
Names namesEntry `json:"names"`
|
||||
} `json:"organizations"`
|
||||
Performers []entityEntry `json:"performers"`
|
||||
PictureFull string `json:"picture_full"`
|
||||
PictureMedium string `json:"picture_medium"`
|
||||
PictureThumb string `json:"picture_thumb"`
|
||||
Platforms []string `json:"platforms"`
|
||||
Products []entityEntry `json:"products"`
|
||||
PublishFormat string `json:"publish_format"`
|
||||
Publisher entityEntry `json:"publisher"`
|
||||
Related []struct {
|
||||
Catalog string `json:"catalog"`
|
||||
Link string `json:"link"`
|
||||
Names namesEntry `json:"names"`
|
||||
Type string `json:"type"`
|
||||
} `json:"related"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
ReleaseEvents []struct {
|
||||
Link string `json:"link"`
|
||||
Name string `json:"name"`
|
||||
ShortName string `json:"shortname"`
|
||||
} `json:"release_events"`
|
||||
ReleasePrice struct {
|
||||
Currency string `json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
} `json:"release_price"`
|
||||
//reprints
|
||||
Votes int `json:"votes"`
|
||||
Websites map[string][]struct {
|
||||
Link string `json:"link"`
|
||||
Name string `json:"name"`
|
||||
} `json:"websites"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(body, result)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
album := &metadata.Album{
|
||||
License: s.GetLicense(),
|
||||
SourceUniqueIdentifier: baseURL + fmt.Sprintf("album/%d", id),
|
||||
}
|
||||
|
||||
getNamesFromNameEntry := func(entry namesEntry) (results map[string]string) {
|
||||
results = make(map[string]string)
|
||||
if len(entry.English) > 0 {
|
||||
results["english"] = entry.English
|
||||
if len(entry.Japanese) > 0 && entry.Japanese != entry.English {
|
||||
results["japanese"] = entry.Japanese
|
||||
}
|
||||
if len(entry.Romaji) > 0 && entry.Romaji != entry.English && entry.Japanese != entry.Romaji {
|
||||
results["romaji"] = entry.Romaji
|
||||
}
|
||||
} else if len(entry.Japanese) > 0 {
|
||||
if entry.Romaji == entry.Japanese {
|
||||
results["romaji"] = entry.Romaji
|
||||
} else {
|
||||
results["japanese"] = entry.Japanese
|
||||
if len(entry.Romaji) > 0 && entry.Romaji != entry.Japanese {
|
||||
results["romaji"] = entry.Romaji
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
addEntityRoles := func(roleName string, entities []entityEntry) {
|
||||
for _, r := range entities {
|
||||
role := metadata.Role{
|
||||
Kind: roleName,
|
||||
}
|
||||
for k, n := range getNamesFromNameEntry(r.Names) {
|
||||
role.Name = append(role.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
|
||||
if len(r.Link) > 0 {
|
||||
role.Name = append(role.Name, metadata.Name{
|
||||
Kind: "url",
|
||||
Name: baseURL + r.Link,
|
||||
})
|
||||
}
|
||||
album.Roles = append(album.Roles, role)
|
||||
}
|
||||
}
|
||||
|
||||
album.Name = append(album.Name, metadata.Name{
|
||||
Kind: "original",
|
||||
Name: result.Name,
|
||||
})
|
||||
|
||||
for k, n := range getNamesFromNameEntry(result.Names) {
|
||||
if result.Name != n {
|
||||
album.Name = append(album.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addEntityRoles("arranger", result.Arrangers)
|
||||
addEntityRoles("composer", result.Composers)
|
||||
addEntityRoles("lyrics", result.Lyricists)
|
||||
addEntityRoles("performer", result.Performers)
|
||||
|
||||
for _, o := range result.Organizations {
|
||||
org := metadata.Role{
|
||||
Kind: o.Role,
|
||||
}
|
||||
for k, n := range getNamesFromNameEntry(o.Names) {
|
||||
org.Name = append(org.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
if len(o.Link) > 0 {
|
||||
org.Name = append(org.Name, metadata.Name{
|
||||
Kind: "url",
|
||||
Name: baseURL + o.Link,
|
||||
})
|
||||
}
|
||||
album.Roles = append(album.Roles, org)
|
||||
}
|
||||
|
||||
publisher := metadata.Role{
|
||||
Kind: "publisher",
|
||||
}
|
||||
for k, n := range getNamesFromNameEntry(result.Publisher.Names) {
|
||||
publisher.Name = append(publisher.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
if len(result.Publisher.Link) > 0 {
|
||||
publisher.Name = append(publisher.Name, metadata.Name{
|
||||
Kind: "url",
|
||||
Name: baseURL + result.Publisher.Link,
|
||||
})
|
||||
}
|
||||
if len(publisher.Name) > 0 {
|
||||
album.Roles = append(album.Roles, publisher)
|
||||
}
|
||||
|
||||
if len(result.Catalog) > 0 {
|
||||
album.Identifiers = append(album.Identifiers, metadata.Name{
|
||||
Kind: "catalog",
|
||||
Name: result.Catalog,
|
||||
})
|
||||
}
|
||||
|
||||
if len(result.Link) > 0 {
|
||||
album.Identifiers = append(album.Identifiers, metadata.Name{
|
||||
Kind: "url",
|
||||
Name: baseURL + result.Link,
|
||||
})
|
||||
}
|
||||
|
||||
for _, e := range result.ReleaseEvents {
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: "release",
|
||||
Name: []metadata.Name{
|
||||
{Kind: "url", Name: baseURL + e.Link},
|
||||
{Kind: "name", Name: e.Name},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for _, c := range result.Covers {
|
||||
if len(c.Full) > 0 {
|
||||
album.Art = append(album.Art, metadata.Name{
|
||||
Kind: strings.ToLower(c.Name),
|
||||
Name: c.Full,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range strings.Split(result.Classification, ",") {
|
||||
n := strings.ToLower(strings.TrimSpace(g))
|
||||
if len(n) > 0 {
|
||||
album.Tags = append(album.Tags, metadata.Name{
|
||||
Kind: "genre",
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range result.Products {
|
||||
product := metadata.Link{
|
||||
Kind: "work",
|
||||
}
|
||||
for k, n := range getNamesFromNameEntry(p.Names) {
|
||||
product.Name = append(product.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
if len(p.Link) > 0 {
|
||||
product.Name = append(product.Name, metadata.Name{
|
||||
Kind: "url",
|
||||
Name: baseURL + p.Link,
|
||||
})
|
||||
}
|
||||
album.Links = append(album.Links, product)
|
||||
}
|
||||
|
||||
for k, l := range result.Websites {
|
||||
for _, link := range l {
|
||||
album.Links = append(album.Links, metadata.Link{
|
||||
Kind: strings.ToLower(k),
|
||||
Name: []metadata.Name{{Kind: "url", Name: link.Link}},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range result.Related {
|
||||
related := metadata.Link{
|
||||
Kind: "related",
|
||||
}
|
||||
|
||||
if len(r.Catalog) > 0 {
|
||||
related.Name = append(related.Name, metadata.Name{
|
||||
Kind: "catalog",
|
||||
Name: r.Catalog,
|
||||
})
|
||||
}
|
||||
|
||||
for k, n := range getNamesFromNameEntry(r.Names) {
|
||||
related.Name = append(related.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
|
||||
if len(r.Link) > 0 {
|
||||
related.Name = append(related.Name, metadata.Name{
|
||||
Kind: "url",
|
||||
Name: baseURL + r.Link,
|
||||
})
|
||||
}
|
||||
album.Links = append(album.Links, related)
|
||||
}
|
||||
|
||||
album.ReleaseDate, _ = time.ParseInLocation("2006-01-02", result.ReleaseDate, time.UTC)
|
||||
|
||||
for _, d := range result.Discs {
|
||||
disc := metadata.Disc{}
|
||||
for _, t := range d.Tracks {
|
||||
track := metadata.Track{}
|
||||
for k, n := range getNamesFromNameEntry(namesEntry(t.Names)) {
|
||||
track.Name = append(track.Name, metadata.Name{
|
||||
Kind: k,
|
||||
Name: n,
|
||||
})
|
||||
}
|
||||
sub := strings.Split(t.Duration, ":")
|
||||
s1, _ := strconv.ParseInt(strings.TrimLeft(sub[0], "0"), 10, 0)
|
||||
if len(sub) > 2 {
|
||||
//TODO
|
||||
} else if len(sub) > 1 {
|
||||
s2, _ := strconv.ParseInt(strings.TrimLeft(sub[1], "0"), 10, 0)
|
||||
track.Duration = time.Minute*time.Duration(s1) + time.Second*time.Duration(s2)
|
||||
} else {
|
||||
track.Duration = time.Second * time.Duration(s1)
|
||||
}
|
||||
|
||||
disc.Tracks = append(disc.Tracks, track)
|
||||
}
|
||||
|
||||
album.Discs = append(album.Discs, disc)
|
||||
}
|
||||
|
||||
return album
|
||||
}
|
||||
|
||||
func (s *Source) Test() {
|
||||
album := s.FindByTOC(metadata.NewTOCFromString("267453 150 24647 71194 95579 139576 174573 199089 244305"))
|
||||
//album := s.GetAlbumInformation(42266)
|
||||
|
||||
log.Print(album)
|
||||
}
|
Loading…
Reference in a new issue