mirror of
https://github.com/by-jp/www.byjp.me.git
synced 2025-08-08 17:15:56 +01:00
Start to replace Songwhip links
This commit is contained in:
parent
17157e5bab
commit
f336366dc5
10 changed files with 372 additions and 23 deletions
|
@ -5,4 +5,4 @@ slug: 18n24
|
|||
---
|
||||
I search for "Oasis" on Spotify and before even a whole album's worth of tracks is listed, sits <cite>Girls & Boys</cite> by Blur 😂 The "rivalry" continues!
|
||||
|
||||
{{< spotify path="/track/5CeL9C3bsoe4yzYS1Qz8cw" title="Girls & Boys" artist="Blur" url="https://songwhip.com/blur/girls-and-boys2000" >}}
|
||||
{{< music "8a8909c0-291c-4643-977f-17d27f052154" >}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
date: "2016-10-20T14:38:36Z"
|
||||
date: 2016-10-20T14:38:36Z
|
||||
tags:
|
||||
- imported
|
||||
- from-facebook
|
||||
|
@ -8,4 +8,4 @@ tags:
|
|||
---
|
||||
Today this is my jam! (Who am I kidding, Vulfpeck are always my jam)
|
||||
|
||||
{{< spotify path="/track/1SHA4IJyiyNobDOrQzFFXy" title="Animal Spirits" artist="Vulfpeck" >}}
|
||||
{{< music "8893154c-4e9d-44d3-8c77-3db17365ca76" >}}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"artist":"Vulfpeck","title":"Animal Spirits","musicbrainz":"https://musicbrainz.org/recording/8893154c-4e9d-44d3-8c77-3db17365ca76","links":["https://open.spotify.com/track/1SHA4IJyiyNobDOrQzFFXy"]}
|
|
@ -0,0 +1 @@
|
|||
{"artist":"Blur","title":"Girls and Boys","musicbrainz":"https://musicbrainz.org/recording/8a8909c0-291c-4643-977f-17d27f052154","links":["https://open.spotify.com/track/5CeL9C3bsoe4yzYS1Qz8cw"]}
|
18
tools/go.mod
18
tools/go.mod
|
@ -6,8 +6,7 @@ require (
|
|||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/Davincible/goinsta/v3 v3.2.6
|
||||
github.com/PuerkitoBio/goquery v1.9.1
|
||||
github.com/aws/smithy-go v1.20.2
|
||||
github.com/bmatcuk/doublestar v1.3.4
|
||||
github.com/agnivade/levenshtein v1.2.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/grokify/html-strip-tags-go v0.1.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
|
@ -18,14 +17,12 @@ require (
|
|||
github.com/mmcdole/gofeed v1.3.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/text v0.14.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/bluesky-social/indigo v0.0.0-20230504025040-8915cccc3319 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20220901095120-1a01299a2163 // indirect
|
||||
github.com/chromedp/chromedp v0.8.5 // indirect
|
||||
|
@ -39,14 +36,12 @@ require (
|
|||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v0.9.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/ipfs/bbloom v0.0.4 // indirect
|
||||
github.com/ipfs/go-block-format v0.1.2 // indirect
|
||||
github.com/ipfs/go-cid v0.4.0 // indirect
|
||||
github.com/ipfs/go-datastore v0.6.0 // indirect
|
||||
github.com/ipfs/go-detect-race v0.0.1 // indirect
|
||||
github.com/ipfs/go-ipfs-blockstore v1.3.0 // indirect
|
||||
github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect
|
||||
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
|
||||
|
@ -58,11 +53,7 @@ require (
|
|||
github.com/jbenet/goprocess v0.1.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/kr/text v0.1.0 // indirect
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
|
@ -76,24 +67,19 @@ require (
|
|||
github.com/multiformats/go-multihash v0.2.1 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
|
||||
github.com/smartystreets/assertions v1.2.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.7.2 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 // indirect
|
||||
github.com/whyrusleeping/cbor-gen v0.0.0-20230331140348-1f892b517e70 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/goleak v1.1.11 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
)
|
||||
|
|
|
@ -6,18 +6,17 @@ github.com/Davincible/goinsta/v3 v3.2.6 h1:+lNIWU6NABWd2VSGe83UQypnef+kzWwjmfgGi
|
|||
github.com/Davincible/goinsta/v3 v3.2.6/go.mod h1:jIDhrWZmttL/gtXj/mkCaZyeNdAAqW3UYjasOUW0YEw=
|
||||
github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
|
||||
github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
|
||||
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bluesky-social/indigo v0.0.0-20230504025040-8915cccc3319 h1:VCNXRXpgyK3xkaQ8fzL5WzswerwLycke4B9ggLs1uOA=
|
||||
github.com/bluesky-social/indigo v0.0.0-20230504025040-8915cccc3319/go.mod h1:Hc09SUJXAIujaAvq7JXxi8ZQQI887grzPkHgn4JyE1Q=
|
||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
|
||||
|
@ -34,6 +33,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
|
|
26
tools/music-normalizer/filter.go
Normal file
26
tools/music-normalizer/filter.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/agnivade/levenshtein"
|
||||
)
|
||||
|
||||
func filter[T interface{}](items []T, filters ...func(T) bool) []T {
|
||||
var allowed []T
|
||||
for _, item := range items {
|
||||
for _, filter := range filters {
|
||||
if filter(item) {
|
||||
allowed = append(allowed, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed
|
||||
}
|
||||
|
||||
func hasCorrectDetails(trackName, artistName string) func(mbRecording) bool {
|
||||
return func(mr mbRecording) bool {
|
||||
dt := levenshtein.ComputeDistance(trackName, mr.Title)
|
||||
da := levenshtein.ComputeDistance(artistName, mr.ArtistCredit[0].Name)
|
||||
|
||||
return dt+da < 6
|
||||
}
|
||||
}
|
137
tools/music-normalizer/main.go
Normal file
137
tools/music-normalizer/main.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Spotify API credentials (set as environment variables)
|
||||
var spotifyClientID = os.Getenv("SPOTIFY_CLIENT_ID")
|
||||
var spotifyClientSecret = os.Getenv("SPOTIFY_CLIENT_SECRET")
|
||||
|
||||
// Structs to parse Spotify API responses
|
||||
type SpotifyAuthResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type SpotifyTrackResponse struct {
|
||||
Name string `json:"name"`
|
||||
Artists []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"artists"`
|
||||
}
|
||||
|
||||
func getSpotifyAccessToken() (string, error) {
|
||||
authURL := "https://accounts.spotify.com/api/token"
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "client_credentials")
|
||||
|
||||
req, _ := http.NewRequest("POST", authURL, strings.NewReader(data.Encode()))
|
||||
req.SetBasicAuth(spotifyClientID, spotifyClientSecret)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var authResp SpotifyAuthResponse
|
||||
json.Unmarshal(body, &authResp)
|
||||
|
||||
return authResp.AccessToken, nil
|
||||
}
|
||||
|
||||
func getSpotifyTrackMetadata(trackID string) (string, string, error) {
|
||||
token, err := getSpotifyAccessToken()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.spotify.com/v1/tracks/%s", trackID)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var trackResp SpotifyTrackResponse
|
||||
json.Unmarshal(body, &trackResp)
|
||||
|
||||
if len(trackResp.Artists) == 0 {
|
||||
return "", "", fmt.Errorf("no artist found for the track")
|
||||
}
|
||||
|
||||
return trackResp.Name, trackResp.Artists[0].Name, nil
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type musicbrainzData struct {
|
||||
ID string `json:"-"`
|
||||
Composer string `json:"composer,omitempty"`
|
||||
Artist string `json:"artist,omitempty"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"musicbrainz"`
|
||||
Links []string `json:"links"`
|
||||
}
|
||||
|
||||
func fromSpotifyTrack(trackID string) (musicbrainzData, error) {
|
||||
trackName, artistName, err := getSpotifyTrackMetadata(trackID)
|
||||
if err != nil {
|
||||
return musicbrainzData{}, err
|
||||
}
|
||||
|
||||
fmt.Printf("Searching for %s - %s\n", trackName, artistName)
|
||||
mb, err := searchMusicbrainz(trackName, artistName)
|
||||
if err != nil {
|
||||
return mb, err
|
||||
}
|
||||
|
||||
mb.Links = []string{"https://open.spotify.com/track/" + trackID}
|
||||
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Printf("Usage: %s <streaming-music-url>\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
u, err := url.Parse(os.Args[1])
|
||||
check(err)
|
||||
|
||||
var mb musicbrainzData
|
||||
switch {
|
||||
case u.Host == "open.spotify.com" && strings.HasPrefix(u.Path, "/track/"):
|
||||
mb, err = fromSpotifyTrack(u.Path[7:])
|
||||
default:
|
||||
err = fmt.Errorf("unknown URL: %s", u)
|
||||
}
|
||||
check(err)
|
||||
|
||||
f, err := os.OpenFile("./data/music/musicbrainz/"+mb.ID+".json", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
check(err)
|
||||
|
||||
check(json.NewEncoder(f).Encode(mb))
|
||||
}
|
73
tools/music-normalizer/musicbrainz.go
Normal file
73
tools/music-normalizer/musicbrainz.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func searchMusicbrainz(trackName, artistName string) (musicbrainzData, error) {
|
||||
var mb musicbrainzData
|
||||
query := url.QueryEscape(fmt.Sprintf("title:\"%s\" AND artist:\"%s\"", trackName, artistName))
|
||||
searchURL := fmt.Sprintf("https://musicbrainz.org/ws/2/recording/?query=%s&method=advanced&limit=50&fmt=json", query)
|
||||
|
||||
resp, err := http.Get(searchURL)
|
||||
if err != nil {
|
||||
return mb, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return mb, err
|
||||
}
|
||||
|
||||
var res mbResponse
|
||||
if err := json.Unmarshal(body, &res); err != nil {
|
||||
return mb, err
|
||||
}
|
||||
if len(res.Recordings) == 0 {
|
||||
return mb, fmt.Errorf("no results found on MusicBrainz")
|
||||
}
|
||||
|
||||
recs := filter(res.Recordings,
|
||||
hasCorrectDetails(trackName, artistName),
|
||||
)
|
||||
|
||||
sort.Sort(ByDesirability(recs))
|
||||
|
||||
mb.ID = recs[0].ID
|
||||
mb.URL = "https://musicbrainz.org/recording/" + recs[0].ID
|
||||
mb.Title = recs[0].Title
|
||||
mb.Artist = recs[0].ArtistCredit[0].Name
|
||||
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
func stringIs(str string, has ...string) bool {
|
||||
t := strings.ToLower(str)
|
||||
for _, ha := range has {
|
||||
if strings.Contains(t, ha) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseReleaseDate(str string) time.Time {
|
||||
if t, err := time.Parse("2006-01-02", str); err == nil {
|
||||
return t
|
||||
}
|
||||
|
||||
if t, err := time.Parse("2006", str); err == nil {
|
||||
// Go to the last day of the year, as we want more specific dates to take precedence
|
||||
return t.AddDate(1, 0, -1)
|
||||
}
|
||||
|
||||
return time.Time{}
|
||||
}
|
125
tools/music-normalizer/sort.go
Normal file
125
tools/music-normalizer/sort.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type mbResponse struct {
|
||||
Recordings []mbRecording
|
||||
}
|
||||
|
||||
type mbRecording struct {
|
||||
ID string
|
||||
Score int
|
||||
Title string
|
||||
Disambiguation string
|
||||
FirstRelease string `json:"first-release-date"`
|
||||
ArtistCredit []mbArtist `json:"artist-credit"`
|
||||
Releases []mbRelease `json:"releases"`
|
||||
}
|
||||
|
||||
type mbArtist struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type mbRelease struct {
|
||||
ID string
|
||||
Score int
|
||||
Title string
|
||||
Country string
|
||||
Status string
|
||||
Quality string
|
||||
ArtistCredit []mbArtist `json:"artist-credit"`
|
||||
Date string `json:"first-release-date"`
|
||||
}
|
||||
|
||||
type ByDesirability []mbRecording
|
||||
|
||||
func (r ByDesirability) Len() int { return len(r) }
|
||||
func (r ByDesirability) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r ByDesirability) Less(i, j int) bool {
|
||||
return sortInTurn(r[i], r[j],
|
||||
// highScoreFirst,
|
||||
preferGoodRelease,
|
||||
preferNonLiveRecordings,
|
||||
earliestRelease,
|
||||
preferShorterName,
|
||||
deterministicallyByID,
|
||||
)
|
||||
}
|
||||
|
||||
func sortInTurn[T interface{}](a, b T, comparators ...func(T, T) int) bool {
|
||||
for _, comparator := range comparators {
|
||||
switch comparator(a, b) {
|
||||
case -1:
|
||||
return true
|
||||
case 1:
|
||||
return false
|
||||
default:
|
||||
// Try the next comparator
|
||||
}
|
||||
}
|
||||
// Don't swap if they're identical
|
||||
return false
|
||||
}
|
||||
|
||||
func ternary(first, last bool) int {
|
||||
if first {
|
||||
return -1
|
||||
} else if last {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func highScoreFirst(a, b mbRecording) int {
|
||||
as := a.Score / 20
|
||||
bs := b.Score / 20
|
||||
return ternary(as > bs, as < bs)
|
||||
}
|
||||
|
||||
func preferNonLiveRecordings(a, b mbRecording) int {
|
||||
al := stringIs(a.Disambiguation, "live")
|
||||
bl := stringIs(b.Disambiguation, "live")
|
||||
return ternary(!al && bl, al && !bl)
|
||||
}
|
||||
|
||||
func preferGoodRelease(a, b mbRecording) int {
|
||||
filter := func(r mbRelease) bool {
|
||||
if len(r.ArtistCredit) > 0 && stringIs(r.ArtistCredit[0].Name, "various") {
|
||||
return false
|
||||
}
|
||||
if !stringIs(r.Status, "official") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
ar := filterReleases(a, filter)
|
||||
br := filterReleases(a, filter)
|
||||
return ternary(!ar && br, ar && !br)
|
||||
}
|
||||
|
||||
func preferShorterName(a, b mbRecording) int {
|
||||
return ternary(len(a.Title) < len(b.Title), len(a.Title) > len(b.Title))
|
||||
}
|
||||
|
||||
func filterReleases(r mbRecording, filter func(mbRelease) bool) bool {
|
||||
for _, rel := range r.Releases {
|
||||
if filter(rel) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func earliestRelease(a, b mbRecording) int {
|
||||
ad := parseReleaseDate(a.FirstRelease)
|
||||
bd := parseReleaseDate(b.FirstRelease)
|
||||
return ternary(ad.Before(bd), ad.After(bd))
|
||||
}
|
||||
|
||||
func deterministicallyByID(a, b mbRecording) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
}
|
Loading…
Reference in a new issue